本文主要讲解 JUC ---- java.util.concurrent 中的一些常见类. concurrent 就是并发的意思, 所以该类中放的都是一些多线程并发编程, 常常使用到的东西.
关注收藏, 开始学习吧
Callable interface 也是一种创建线程的方式, 相当于把线程封装了一个 “返回值”. 方便程序员借助多线程的方式计算结果. 他与之前 Thread 使用 Runable 创建线程有些不同, 并且使用 Callable 不能直接作为 Thread 构造方法的参数.
代码示例: 创建线程计算 1 + 2 + 3 + … + 1000, 不使用 Callable 版本
public class ThreadDemo26 {
static class Result {
public int sum = 0;
public Object locker = new Object();
}
public static void main(String[] args) throws InterruptedException {
Result result = new Result();
Thread thread = new Thread() {
@Override
public void run() {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
synchronized (result.locker) {
result.sum = sum;
result.locker.notify();
}
}
};
thread.start();
synchronized (result.locker) {
while (result.sum == 0) {
result.locker.wait();
}
System.out.println(result.sum);
}
}
}
可以看到, 上述代码需要一个辅助类 Result, 还需要使用一系列的加锁和 wait notify 操作, 代码复杂, 容易出错.
代码示例: 创建线程计算 1 + 2 + 3 + … + 1000, 使用 Callable 版本
futureTask.get()
能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.public class ThreadDemo27 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
int result = futureTask.get();
System.out.println(result);
}
}
可以看到, 使用 Callable 和 FutureTask 之后, 代码简化了很多, 也不必手动写线程同步代码了.
理解 Callable
理解 FutureTask
“Reentrant” 这个单词的原意就是 “可重入”, 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全. 这个锁没有 synchronized 那么常用, 在使用上更接近 C++ 中的 mutex 锁.
ReentrantLock 的用法:
ReentrantLock lock = new ReentrantLock();
-----------------------------------------
lock.lock();
try {
// working
} finally {
lock.unlock()
}
ReentrantLock 和 synchronized 的区别:
当然, ReentrantLock 也有很大的劣势
所以在实际开发中, 用到锁一般优先考虑 synchronized. 在以下两个情况时, 可以优先考虑使用 ReentrantLock.
原子类, 我们在讲 CAS 部分时介绍过. 原子类内部用的是 CAS 实现, 所以性能要比加锁实现 i++ 高很多. 原子类主要有以下几个
那么在开发中, 原子类主要应用场景有哪些呢?
线程池在我之前的一篇文章中重点讲述过, 其用法在这里就不多做介绍了.
Semaphore 信号量, 也是并发编程中的一个重要组件, 在操作系统中也经常出现. 本质上就是一个计数器, 用来表示 “可用资源的个数”. 描述的是, 当前该线程, 是否有 “临界资源” 可使用.
临界资源, 指多个线程 / 进程等并发执行的实体, 可以公共使用到的资源.
理解信号量
acquire
申请.release
释放.Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.
代码示例
public class ThreadDemo28 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(4);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("申请资源");
semaphore.acquire();
System.out.println("我获取到资源了");
Thread.sleep(2000);
System.out.println("释放资源");
semaphore.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(runnable);
thread.start();
}
}
}
CountDownLatch 是用来针对特定场景的一个组件, 可以同时等待多个任务执行结束. 好像跑步比赛, 10个选手依次就位, 哨声响才同时出发. 所有选手都通过终点, 才能公布成绩.
代码示例
public class ThreadDemo29 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("一名选手回来");
countDownLatch.countDown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
countDownLatch.await();
System.out.println("game over");
}
}
✨ 本文主要讲解了 JUC 中的一些常见类, 需要掌握 Callable 接口, ReentrantLock 锁, 原子类, 线程池, 信号量以及 CountDownLatch.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.
再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!