CountDownLatch倒数门栓
CountDownLatch倒数门栓,它允许一个或多个线程等待,直到在其他线程执行的一组操作完成为止。
CountDownLatch(int count): CountDownLatch类只有一个构造函数,count为倒数的数值。
await(): 调用await()方法的线程会被挂起,此线程会等待直到count值为0才继续执行。
countDown(): 将count值减一,直到为0时,等待的线程才会被唤起。
/**
* CountDownLatch用法一:一个线程等待多个线程都执行完毕,再继续自己的工作
* 例如嫦娥五号发射前要进行各项检查,检查完毕后发射
*/
class CountDownLatchDemo01{
public static void main(String[] args) throws InterruptedException {
/**
* 倒数计数器设置为3
*/
CountDownLatch latch = new CountDownLatch(3);
System.out.println("嫦娥五号进入发生塔,请各单位开始发射前检查");
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"检查完毕");
/**
* 执行countDown()方法,latch的计数器减一
*/
latch.countDown();
}, "单位"+i).start();
}
/**
* 等待各单位检查完毕,即latch计数器为0
*/
latch.await();
System.out.println("发射,飞向月球");
}
}
CyclicBarrier循环栅栏
CyclicBarrier可以构造一个栅栏,当一组线程执行到栅栏处,会被栅栏阻挡,栅栏打开,这组线程统一执行。打个比方,运动员们走上起跑线后等待,裁判鸣枪,运动员起跑。裁判鸣枪就有点类似栅栏。CyclicBarrier与CountDownLatch的不同之处是,CountDownLatch的计数器倒数至0后,就不能再次使用了,但CyclicBarrier没有倒数计数器,是可以重复使用的。
/**
* 运动员起跑的例子
* 为了演示CyclicBarrier可以重复使用的特性,这例子有点特殊之处。
* 有10个运动员走上跑道,只要有5个运动员准备完毕,裁判就会扣响发令枪,这5个运动员起跑。
* 再有5个运动员准备完毕,裁判还能扣响发令枪,5个运动员起跑。
*/
class CyclicBarrierDemo{
public static void main(String[] args) {
/**
* 构造一个循环栅栏,类似裁判。
* 有5个运动员准备完毕,裁判便会鸣枪
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
System.out.println("########裁判扣响发令枪");
});
/**
* 构造10个运动员
*/
for (int i = 0; i < 10; i++) {
new Thread(new Task(cyclicBarrier), "运动员"+i).start();
}
}
static class Task implements Runnable{
private CyclicBarrier cyclicBarrier;
public Task(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(5000));
System.out.println(Thread.currentThread().getName()+"准备完毕");
/**
* 等待裁判鸣枪
*/
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()+"起跑");
}catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Semaphore计数信号量
Semaphore的作用是维护一个“许可证”的计数,线程可以获取许可证,许可证数量减一;线程也可以释放许可证,许可证数量加一,当Semaphore的许可证数量减为0,下一个想要获取许可证的线程就需要等待,直到有另外的线程释放了许可证。Semaphore并不要求获取许可证的线程释放许可证,A线程获取一个许可证,B线程释放许可证也是可以的。
Semaphore重要方法介绍
Semaphore(int permits),permits是许可证数量,即可同时运行的线程数。
new Semaphore(int permits, boolean fair),fair是否使用公平策略。
tryAcquire(),尝试获取一个许可,获取成功返回true,获取失败返回false。
tryAcquire(timeout),和tryAcquire()一样,但是多了一个超时时间,指定时间内未获取到许可证就返回false
acquire(),获取许可证,获取不到就阻塞,可以被中断
acquireUninterruptibly(),和acquire()一样,但是不能响应中断
release(),归还许可证
acquire(int n),一次可以拿多个许可证
release(int n),一次释放多个许可证。如无特殊需求,获取和释放的数量要一致
/**
* 使用Semaphore模拟银行窗口办理业务
*/
class SemaphoreDemo{
/**
* 创建有3个许可证的Semaphore。类似银行有3个窗口,只能同时办理3个人的业务
*/
static Semaphore semaphore = new Semaphore(3);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
/**
* acquire()获取许可证
*/
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"去窗口办理业务");
TimeUnit.SECONDS.sleep(new Random().nextInt(5)+1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
/**
* release()释放许可证
*/
semaphore.release();
System.out.println(Thread.currentThread().getName()+"业务办理完毕,离开");
}
}, "排队的人"+i).start();
}
}
}
Condition
如果说Lock用来替代synchronized,那么Condition就是用来替代Object.wait()/notify()。Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify();
/**
* Condition实现生产者消费者模式
*/
class ConditionDemo {
private int queueSize = 100;
private PriorityQueue<Integer> queue = new PriorityQueue<>();
private Lock lock = new ReentrantLock();
/**
* ReentrantLock可以创建多个Condition条件
*/
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
ConditionDemo condition02 = new ConditionDemo();
Consumer consumer = condition02.new Consumer();
Producer producer = condition02.new Producer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
consume();
}
private void consume(){
while (true){
lock.lock();
try {
if (queue.size() ==0){
System.out.println("队列空,等待生产数据");
notEmpty.await();
}
Integer e = queue.poll();
System.out.println("消费数据 "+e+" ,唤醒生产者");
notFull.signalAll();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce(){
while (true){
lock.lock();
try {
if (queue.size() == queueSize){
System.out.println("队列满了,等待消费数据");
notFull.await();
}
int i = new Random().nextInt(100);
queue.add(i);
System.out.println("生产数据 "+i+" ,唤醒消费者");
notEmpty.signalAll();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
}
}
AQS
AQS全称AbstractQueuedSynchronizer,是一个用于构建锁、同步器、协作类的工具类(框架)。有了AQS,构建线程协作类就容易多了。
Semaphore、CountDownLatch、ReentrantLock都使用到了AbstractQueuedSynchronizer,这些类中都有一个内部类Sync,Sync继承了AbstractQueuedSynchronizer。CyclicBarrier使用ReentrantLock实现,也间接地用到了AbstractQueuedSynchronizer。
AQS三大核心:
1、private volatile int state;
state的具体含义,会根据具体实现类的不同而不同。
比如在Semaphore中,state表示剩余的许可证数量。
而在CountDownLatch中,state表示倒数计数器。
在ReentrantLock中表示锁的重入次数。
state为0表示锁不被任何线程占有。
state会被并发地修改,所有修改state的方法都要保证线程安全,比如getState()、setState(int newState)、compareAndSetState(int expect, int update)。
2、阻塞队列
这个队列用来存放“等待的线程”,当线程拿不到锁而阻塞时,将这些线程放到FIFO阻塞队列中。队列使用Node类构建,部分源码如下:
static final class Node {
/**
* 前一个节点
*/
volatile Node prev;
/**
* 下一个节点
*/
volatile Node next;
/**
* 等待的线程
*/
volatile Thread thread;
/**
* 队列头结点
*/
private transient volatile Node head;
/**
* 队列的尾部节点
*/
private transient volatile Node tail;
}
3、获取、释放方法。
这类方法由线程协作工具类自己实现,并且含义各不相同。
在Semaphore中,获取是acquire方法,获取一个许可证,释放是release方法,释放一个许可证。
在CountDownLatch中,获取是await方法,等待倒数结束,释放是countDown方法,计数器减一。
在ReentrantLock中,获取是lock方法,获取锁,释放是unlock方法,释放锁。
使用AQS构建自己的线程协作类一般有三步:
1、线程协作类内部写一个Sync类继承AbstractQueuedSynchronizer。
2、独占锁重写tryAcquire(int arg)、tryRelease(int arg);共享锁重写tryAcquireShared(int acquires)、tryReleaseShared(int release)
3、线程协作类中写获取、释放方法,并调用重写的方法。
AQS在CountDownLatch中的应用
CountDownLatch构造函数源码解析
/**
* CountDownLatch构造函数
* 内部类Sync继承了AbstractQueuedSynchronizer
* 创建Sync对象,赋值给CountDownLatch的sync属性
* 倒数计数器值count设置给AbstractQueuedSynchronizer的state属性
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
await()方法源码解析
调用了CountDownLatch重写的tryAcquireShared(int acquires)
protected int tryAcquireShared(int acquires) {
// AQS的state为0返回1,不为0返回-1
return (getState() == 0) ? 1 : -1;
}
state != 0时,tryAcquireShared(int acquires)返回结果小于0,则会调用AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(),此方法做了两件事:
1、用当前线程创建一个Node对象形成队列,当前的线程便进入了队列中。
2、调用AbstractQueuedSynchronizer.parkAndCheckInterrupt()阻塞当前线程。
await()方法调用栈如下:
parkAndCheckInterrupt:837, AbstractQueuedSynchronizer (java.util.concurrent.locks)
doAcquireSharedInterruptibly:997, AbstractQueuedSynchronizer (java.util.concurrent.locks)
acquireSharedInterruptibly:1304, AbstractQueuedSynchronizer (java.util.concurrent.locks)
await:231, CountDownLatch (java.util.concurrent)
countDown()方法源码解析
调用了CountDownLatch重写的tryReleaseShared(int releases)
protected boolean tryReleaseShared(int releases) {
// 自旋
for (;;) {
int c = getState();
// 如果state == 0,直接返回
if (c == 0)
return false;
// 使用CAS将state值减一,如果state-1==0,最后一次倒数结束,则返回true
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
tryReleaseShared(int releases)返回true,则会调用AbstractQueuedSynchronizer.doReleaseShared()在此方法中唤醒队列中等待的线程。
AQS在Semaphore中的应用
Semaphore的构造函数有两个,Semaphore(int permits)、Semaphore(int permits, boolean fair)可设置公平性与非公平性。Semaphore内部同样有Sync内部类,并且还有FairSync、NonfairSync这两个类用于实现获取许可证的公平与非公平性。
acquire()方法源码解析
此方法调用tryAcquireShared(int acquires)方法,FairSync、NonfairSync的tryAcquireShared(int acquires)方法的差异是FairSync的tryAcquireShared(int acquires)方法会判断阻塞队列是否有线程,若有,则返回-1。
protected int tryAcquireShared(int acquires) {
for (;;) {
/**
* FairSync的tryAcquireShared(int acquires)多了以下的判断
* 如果队列中有排队线程,则返回-1
* 有关公平性与非公平性可查看我这篇博客 https://blog.youkuaiyun.com/u010606397/article/details/109247667
*/
if (hasQueuedPredecessors())
return -1;
// 在Semaphore中,state表示可用许可证的数量
int available = getState();
// 剩余数量 = 可用许可证数量 - 线程需要的许可证数量
int remaining = available - acquires;
// 如果剩余数量 < 0 意味着可用许可证数量不够,不去修改state的值,直接返回remaining
// 如果剩余数量 >= 0 意味许可证数量够用,使用“CAS+自旋”修改state值
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
tryAcquireShared(int acquires)返回结果小于0,意味着当前线程没获取到许可证。执行AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(int arg),在此方法中将线程加入阻塞队列中,并将当前线程阻塞。这部分的逻辑跟CountDownLatch的await()方法很类似。
release()方法源码解析
调动Semaphore.Sync.releaseShared(int arg)
public final boolean releaseShared(int arg) {
// 1、tryReleaseShared(arg)释放操作,即归还许可证
if (tryReleaseShared(arg)) {
// 2、唤醒阻塞队列中等待的线程
doReleaseShared();
return true;
}
return false;
}
AQS在ReentrantLock中的应用
ReentrantLock构造函数
public ReentrantLock() {
// 创建非公平的同步控制类
sync = new ReentrantLock.NonfairSync();
}
public ReentrantLock(boolean fair) {
// 创建公平或非公平的同步控制类
sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
}
lock()方法源码解析
这是获取方法,大概的逻辑是判断state是否为0,state == 0 即锁没被线程持有,state值增加,设置当前线程拥有锁。state != 0 则锁被其他线程持有,当前线程进入队列中排队。
unlock()方法源码解析
这是释放方法,大概的逻辑是,state值减少,当state==0 唤醒队列中等待的线程。