我们通常所说的并发包也就是 java.util.concurrent 及其子包,集中了 Java 并发的各种基础工具类,具体主要包括几个方面:
提供了比 synchronized 更加高级的各种同步结构,包括 CountDownLatch、CyclicBarrier、Semaphore 等,可以实现更加丰富的多线程操作,比如利用 Semaphore 作为资源控制器,限制同时进行工作的线程数量。
各种线程安全的容器,比如最常见的 ConcurrentHashMap、有序的ConcunrrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组CopyOnWriteArrayList 等。
各种并发队列实现,如各种 BlockedQueue 实现,比较典型的 ArrayBlockingQueue、SynchorousQueue 或针对特定场景的 PriorityBlockingQueue 等。
强大的 Executor 框架,可以创建各种不同类型的线程池,调度任务运行等,绝大部分情况下,不再需要自己从头实现线程池和任务调度器。
1. 闭锁:CountDownLatch
1.1 使用场景
若有多条线程,其中一条线程需要等到其他所有线程准备完所需的资源后才能运行,这样的情况可以使用闭锁。
CountDownLatch允许一个或者多个线程等待其他线程完成操作。
1.2 代码实现
public class CountDownLatchTest {
static CountDownLatch latch = new CountDownLatch(3);
public static void main(String[] args) {
// 初始化闭锁,并设置资源个
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 加载资源1
System.out.println("加载资源的代码1...");
// 本资源加载完后,闭锁-1
latch.countDown();
}).start();
new Thread(() -> {
// 加载资源2
System.out.println("加载资源的代码2...");
// 本资源加载完后,闭锁-1
latch.countDown();
System.out.println("加载资源的代码3...");
}).start();
new Thread( new Runnable(){
public void run(){
// 本线程必须等待所有资源加载完后才能执行
try {
latch.await(6, TimeUnit.SECONDS);
// 当闭锁数量为0时,await返回,执行接下来的任务
System.out.println("资源加载完毕,执行任务...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ).start();
}
}
/**
执行结果:
加载资源的代码2...
加载资源的代码3...
加载资源的代码1...
资源加载完毕,执行任务...
*/
countDown方法每调用一次,节点数减一,直到节点数为0时,等待的线程被唤醒。由于countDown可以用在任何地方,所以这里说的N个节点,可以是N个线程,也可以是一个线程里的N个执行步骤。
await还有一个重载方法await(long time, TimeUnit unit),超时不等待
2. 同步屏障:CyclicBarrier
2.1 使用场景
若有多条线程,他们到达屏障时将会被阻塞,只有当所有线程都到达屏障时才能打开屏障,所有线程同时执行,若有这样的需求可以使用同步屏障。此外,当屏障打开的同时还能指定执行的任务。
可以用于多线程计算数据,最后合并计算结果的场景。每个线程计算完毕之后插入一个屏障,都处理完之后在执行一个run方法去处理汇总后的计算结果。
2.2 闭锁 与 同步屏障 的区别
- 闭锁只会阻塞一条线程,目的是为了让该条任务线程满足条件后执行;
- 而同步屏障会阻塞所有线程,目的是为了让所有线程同时执行(实际上并不会同时执行,而是尽量把线程启动的时间间隔降为最少)。
- CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。
2.3 代码实现
public class BarrierTest {
// 创建同步屏障对象,并制定需要等待的线程个数 和 打开屏障时需要执行的任务
static CyclicBarrier barrier = new CyclicBarrier(3,new Runnable(){
public void run(){
//当所有线程准备完毕后触发此任务
System.out.println("准备好了");
}
});
public static void main(String[] args) {
// 启动三条线程
for( int i=0; i<3; i++ ){
new Thread( new Runnable(){
public void run(){
// 等待,(每执行一次barrier.await,同步屏障数量-1,直到为0时,打开屏障)
try {
System.out.println("屏障未打开");
barrier.await();
System.out.println("屏障打开");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
} ).start();
}
}
}
/**
屏障未打开
屏障未打开
屏障未打开
准备好了
屏障打开
屏障打开
屏障打开
* */
3. 信号量:Semaphore
3.1 使用场景
若有m个资源,但有n条线程(n>m),因此同一时刻只能允许m条线程访问资源,此时可以使用Semaphore控制访问该资源的线程数量。
信号量是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
3.2 代码实现
public class SemaphoreTest {
// 创建信号量对象,并给予3个资源
static Semaphore semaphore = new Semaphore(3);
private static ExecutorService threadPool = Executors.newCachedThreadPool();
public static void main(String[] args) {
// 开启10条线程
for ( int i=0; i<10; i++ ) {
final int j=i;
threadPool.execute(() -> {
try {
// 获取资源,若此时资源被用光,则阻塞,直到有线程归还资源
semaphore.acquire();
// 任务代码
TimeUnit.SECONDS.sleep(2);
System.out.println("任务执行完毕"+j+",等待的线程数"+semaphore.getQueueLength());
// 释放资源
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
/**
任务执行完毕1,等待的线程数7
任务执行完毕3,等待的线程数6
任务执行完毕5,等待的线程数5
任务执行完毕0,等待的线程数4
任务执行完毕2,等待的线程数3
任务执行完毕4,等待的线程数2
任务执行完毕7,等待的线程数1
任务执行完毕6,等待的线程数0
任务执行完毕8,等待的线程数0
任务执行完毕9,等待的线程数0
*/
4、线程间交换数据的Exchanger
4.1简介
Exchanger(交换者)是一个用于线程间协作的工具类,用于进行线程间的数据交换。
4.2使用场景
可以用于遗传算法,也可以用于校对工作,查看两个线程返回的数据是否一致。
public class ExchangerTest {
private static final Exchanger<String> exgr = new Exchanger<>();
private static ExecutorService threadPool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
threadPool.execute(() -> {
try {
String A = "银行流水A";
exgr.exchange(A);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadPool.execute(() -> {
try {
String B = "银行流水B";
String A = exgr.exchange(B);
System.out.println("A和B数据是否一致:" + A.equals(B) + ",A的录入是"+ A + ",B录入的是" + B);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadPool.shutdown();
}
}
/**
A和B数据是否一致:false,A的录入是银行流水A,B录入的是银行流水B
* */