1、JUC简介
在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,
用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等,大大的提高了java的并发性能。
2、JUC之AQS
AQS(AbstractQueuedSynchronizer)是JUC的核心,其特点:
(1)使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基本框架;
(2)利用了一个int型表示状态;
(3)使用方法是继承
(4)子类通过继承并通过实现它的方法管理其状态(acquire和release)的方法操纵状态
(5)可以同时实现排它锁和共享锁模式(独占、共享)
下面是AQS数据结构的底层实现:
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 | |
AQS(AbstractQueuedSynchronizer)原理图:
3、CountDouwnLatch
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
CountDownLatch 的两种典型用法:
- CountDouwnLatch相当于一个计数器,是一个原子操作,调用了该类的await()方法的线程会一直处于阻塞的状态,直到其他线程调用countDown()方法使得计数器的值等于0,调用了该类的await()方法线程机会重新执行。但是该过程只能执行一次,因为计数器的值不能重(CyclicBarrier类可以重置)
- 实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的
CountDownLatch 对象,将其计数器初始化为 1 :new CountDownLatch(1),多个线程在开始执行任务前首先
coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
4、Semaphore
Semaphore(信号量)可以指定多个线程同时访问某个资源,维护资源的访问个数。Semaphore通过acquire()获取许可,release()方法释放许可,Semaphore经常用于限制获取某种资源的线程数量。
Semaphore 有两种模式:
- 公平模式: 调用acquire的顺序就是获取许可证的顺序,遵循FIFO;
- 非公平模式: 抢占式的。
5、CyclicBarrier
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier 的应用场景:
CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。
public class CyclicBarrierExample2 {
// 请求的数量
private static final int threadCount = 550;
// 需要同步的线程数量
private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) throws InterruptedException {
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
Thread.sleep(1000);
threadPool.execute(() -> {
try {
test(threadNum);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
}
threadPool.shutdown();
}
public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
System.out.println("threadnum:" + threadnum + "is ready");
try {
cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);
} catch (Exception e) {
System.out.println("-----CyclicBarrierException------");
}
System.out.println("threadnum:" + threadnum + "is finish");
}
}
注意:
CyclicBarrier和CountDownLatch的区别:
CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。
对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。
CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。
6 ReentrantLock
java里面的锁主要分为两类,一是Synchronized修饰的锁,二是JUC提供的锁。
这里说说ReentrantLock与Synchronized区别:
(1)可重入性
(2)锁的实现方式
(3)性能的区别
(4)功能的区别
ReentrantLock独有的功能:
(1) 可指定是公平锁还是非公平锁
(2) 提供了一个Condition类,可以分组唤醒需要唤醒的线程
(3) 提供能够中断等待锁的线程的机制