JUC 组件
- 前言
- 一、Callable
- 二、ReentrantLock
- 三、Atomic 原子类
- 四、线程池
- 五、Semaphore
- 六、CountDownLatch
前言
JUC(Java.util.concurrent)是 Java 标准库中的一个包,它提供了一组并发编程工具,本篇文章就介绍几组常见的 JUC 组件:Callable、ReentranLock、Atomic原子类、线程池、Semaphore、CountDownLatch。
一、Callable
类似于 Runnable,Callable也是一个 interface,用来描述一个任务。与Runnable接口不同的是Callable接口是描述了一个具有返回值的任务。
📝例如我们创建1个线程计算1到10000的累加和,并且要求返回结果值,我们就可以使用 Callable:
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 使用 Callable 创建一个有返回值的任务
// Callable 带有泛型参数,泛型参数表示返回值的类型。
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10000; i++) {
sum += i;
}
return sum;
}
};
// 使用 FutureTask 包装一下。相当于一张任务凭据,后续可以使用凭据拿到结果。
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的
// call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中
Thread t = new Thread(futureTask);
// 启动线程
t.start();
// 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕.无需使用 join
int sum = futureTask.get();
System.out.println(sum);
}
}

通过上述对Callable接口的简单使用,以及阅读代码中的注释,相信你已经对Callable接口有了一定的理解,那么Callable究竟是什么,下面我们再来总结一下:
Callable 是一个
interface,相当于把线程封装了一个 “返回值”,方便程序猿借助多线程的方式计算结果。另外 Callable 和 Runnable 相似,都是描述一个 “任务”。Callable 描述的是带有返回值的任务,Runnable 描述的是不带返回值的任务。Callable 通常需要搭配 FutureTask 来使用,
FutureTask用来保存 Callable 的返回结果。因为 Callable 往往是在另一个线程中执行的,什么时候执行完并不确定,通过调用 FutureTask 的 get 方法能够阻塞等待新线程计算完毕。
实现Callable接口也是创建线程的新方式,目前为止我们已学过的线程创建如下:
继承 Thread 类:继承 Thread 类并重写其中的 run() 方法,然后创建 Thread 的子类实例并调用 start() 方法即可启动一个新线程。
实现 Runnable 接口:实现 Runnable 接口并重写其中的 run() 方法,然后创建 Thread 实例时传入该 Runnable 对象并调用 start() 方法即可启动一个新线程。
实现 Callable 接口:实现 Callable 接口并重写其中的 call() 方法,然后创建 FutureTask 对象并将其作为参数传入 Thread 构造函数中,再调用 start() 方法即可启动一个新线程。
二、ReentrantLock
synchronized 和 ReentrantLock 都是用于实现多线程同步的工具,它们的目的都是为了保证多个线程对共享资源的安全访问。但是它们的实现机制和用法略有不同:
ReentrantLock 和 synchronized 的区别
synchronized关键字是JVM内部实现的。ReentrantLock是标准库的一个类,是JVM外部实现的。synchronized 是基于 代码块 的方式来控制加锁的,不需要手动释放锁。ReentrantLock 提供了
lock,unlock独立的方法,来进行加锁解锁,使用起来更灵活,但是需要手动释放锁。synchronized 在申请锁失败时,会 死等。ReentrantLock 可以通过
trylock的方式等待一段时间就放弃加锁。synchronized 是非 公平锁。ReentrantLock 默认是 非公平锁,但它可以通过构造方法传入一个
true开启公平锁模式。synchronized 搭配 wait、notify 进行等待唤醒,如果多个线程 wati 同一个对象,notify 将随机唤醒一个。ReentrantLock 则是搭配 Condition 这个类,这个类也能起到等待-唤醒,但是功能更强大,可以更精确控制唤醒某个指定的线程。
Tips:当然在大部分情况下 synchronized 就足够了,但是 ReentrantLock 是一个重要补充!
三、Atomic 原子类
JUC中还提供了一些原子类,原子类内部用的是 CAS 实现,性能要比加锁高得多:
- AtomicBoolean
- AtomicInteger
- AtomicIntegerArray
- AtomicLong
- AtomicReference
- AtomicStampedReference
这些原子类大家了解,可以简单使用即可,这里就不做过多的展开介绍了。
四、线程池
点击这里 --> 转到多线程案例,线程池模块介绍。
五、Semaphore
Semaphore 信号量,用来表示 “可用资源的个数”,本质上就是一个 计数器。
其实信号量就相当于生活中的停车场:
停车场中有一定数量的停车位,假设这个停车场最多只能容纳50辆汽车,那么当停车场中已经有49辆汽车时,如果进来了一辆汽车,就相当于申请一个可用资源,可用车位就 -1。(-1操作称为信号量的P操作)
此时计数器的值已经为 0了,即现在停车场已经没有车位可用了,如果这时再来新的汽车,就需要进行等待,直到有一辆汽车离开,相当于释放一个可用资源,可用车位就+1,此时有了停车位可用,这辆汽车才能够进入停车场停车。(+1操作称为信号量的V操作)
注:Semaphore 的 PV 操作中的加减计数器操作都是原子的,可以在多线程环境下直接使用。
使用示例:
在 JUC 的 Semaphore 中,acquire 方法表示申请资源(P操作),release 方法表示释放资源(V操作)。
public class Demo1 {
public static void main(String[] args) {
// 创建 1 个初始值为 4 的信号量
Semaphore semaphore = new Semaphore(4);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("申请资源");
// acquire 方法表示申请
semaphore.acquire();
System.out.println("我获取到资源了");
Thread.sleep(1000);
System.out.println("我释放资源了");
// release 方法表示释放
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 20; i++) {
Thread t = new Thread(runnable);
t.start();
}
}
}

Semaphore 实际开发中的场景——共享锁
使用信号量可以实现 “共享锁”,比如某个资源允许 3 个线程同时使用, 那么就可以使用 P 操作作为加锁,V 操作作为解锁,前三个线程的 P 操作都能顺利返回,后续线程再进行 P 操作就会阻塞等待,直到前面的线程执行了 V 操作。
六、CountDownLatch
CountDownLatch 线程同步工具类,可以理解为同时等待 N 个任务执行结束。
就例如跑步比赛,10个选手依次就位,哨声响才同时出发,所有选手都通过终点,才能公布成绩,即比赛结束取决于最后一个选手冲过终点。
具体实现:
(1)构造 CountDownLatch 实例 latch,初始化 10 表示有 10 个任务需要完成.
(2)每个任务执行完毕,都调用 latch.countDown() ,在 CountDownLatch 内部的计数器同时自减。
(3)主线程中使用 latch.await(); 阻塞等待所有任务执行完毕,当计数器为 0 了,阻塞就解除,继续进行后续操作。
public class Demo2 {
public static void main(String[] args) throws Exception {
// 创建 1 个原子类,用于多线程计数
AtomicInteger atomicInteger = new AtomicInteger(1);
// 创建 1 个初始值为 10 的线程同步工具类
CountDownLatch latch = new CountDownLatch(10);
Runnable r = new Runnable() {
@Override
public void run() {
try {
// 生成一个 0-9999之间的随机数,表示比赛时间
Thread.sleep((long) (Math.random() * 10000));
System.out.println("第: "+atomicInteger.getAndIncrement()+"选手完成了比赛");
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
// 必须等到 10 人全部回来
latch.await();
System.out.println("比赛结束");
}
}





















