1、CountDownLatch
CountDownLatch是一个同步工具类,能够使一个线程等待其他一些线程执行完成后,再继续执行!CountDownLatch使用计数器实现。计数器初始值为线程的数量。当每个线程执行完自己任务后,计数器减一。当计数器的值为0时,表示所有的线程都已经完成,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。
主要方法列表:
//使用计数器初始化CountDownLatch
public CountDownLatch(int count)
// 使当前线程在计数器归零前一直等待,除非线程被中断。
void await()
// 使当前线程在计数器归零前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减计数器,如果计数到达零,则释放等待的线程。
void countDown()
源码解析:
CountDownDownLatch构造方法:
通过源码可以看到内部类Sync,继承自AbstractQueuedSynchronizer
public class CountDownLatch {
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
//其中Sync是内部类
this.sync = new Sync(count);
}
//内部变量
private final Sync sync;
}
CountDownDownLatch ------ await()方法:
await()方法实际上是调用的AQS的acquireSharedInterruptibly(1);
如果当前线程是中断状态,则抛出InterruptedException异常,否则,尝试获取共享锁
// java.util.concurrent.CountDownLatch.await()
public void await() throws InterruptedException {
// 调用AQS的acquireSharedInterruptibly()方法
sync.acquireSharedInterruptibly(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly()
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) //判断是否发生中断
throw new InterruptedException();
// 尝试获取锁,如果失败则排队
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
CountDownDownLatch ------ countDown()方法:
// java.util.concurrent.CountDownLatch.countDown()
public void countDown() {
// 调用AQS的释放共享锁方法
sync.releaseShared(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared()
public final boolean releaseShared(int arg) {
// 尝试释放共享锁,如果成功了,就唤醒排队的线程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
使用场景:
CountDownLatch使用示例:百米赛跑模拟
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchTest {
private static final int RUNNER_COUNT = 10;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch begin = new CountDownLatch(1);
final CountDownLatch end = new CountDownLatch(RUNNER_COUNT);
final ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i = 0; i < RUNNER_COUNT; i++) {
final int NO = i + 1;
Runnable run = new Runnable() {
@Override
public void run() {
try {
begin.await();
Thread.sleep((long)(Math.random() * 10000));
System.out.println("No." + NO + " arrived");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("Game Start ...");
begin.countDown();
end.await();
// end.await(30, TimeUnit.SECONDS);
System.out.println("Game Over.");
exec.shutdown();
}
}
运行结果:
Game Start ... No.5 arrived No.2 arrived No.10 arrived No.6 arrived No.1 arrived No.8 arrived No.3 arrived No.9 arrived No.7 arrived No.4 arrived Game Over.
从运行结果可以看到,等所有线程运行完后,game over!
2、CyclicBarrier
栅栏,通过它可以让一组线程等待至某个状态之后再全部一起执行,举个例子:还是百米赛跑,需要每个运动准备好了,才能鸣枪开始。
CyclicBarrier不是基于AQS实现的
主要方法列表:
//参与的线程个数,第二个构造方法有一个 Runnable 参数,这个参数是最后一个线程到达时要做的任务
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
源码解析:
public class CyclicBarrier {
//没有自定义同步器,而是定义一个Generation类,里面包含一个broker属性
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry 可重入锁*/
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
/** The number of parties 参与人数量*/
private final int parties;
/* The command to run when tripped 触发时要运行的命令,即执行额外的操作 */
//在构造函数的时候赋值,它的用处是当count清零后,
//也就是所有线程都执行到await()方法,其中一个线程拿到锁后,是否执行额外的操作,还是继续执行自身逻辑。
private final Runnable barrierCommand;
/** The current generation */
private Generation generation = new Generation();
/**
* Number of parties still waiting. Counts down from parties to 0
* on each generation. It is reset to parties on each new
* generation or when broken.
* 记录还有多少在等待
*/
private int count;
}
入口方法await():
//重载方法1
public int await() throws InterruptedException, BrokenBarrierException {
try {
//调用核心方法
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//重载方法2
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
//调用核心方法
return dowait(true, unit.toNanos(timeout));
}
核心方法dowait():
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//获取锁
lock.lock();
try {
final Generation g = generation;
//判断状态
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//打破屏障,本身线程让count减一
int index = --count;
//判断是否打到临界状态
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//检测额外动作时候有,如果不为空,则执行额外动作
if (command != null)
command.run();
ranAction = true;
//进入下一个纪元,方法内部有调用 signalAll()唤醒所有阻塞的线程
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
//未达到临界状态,开始阻塞,自旋
// loop until tripped, broken, interrupted, or timed out
//自旋直到tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed) //判断是否带有超时的阻塞
trip.await(); //不带超时,一直阻塞
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);//带超时,设置超时时间
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
// 如果屏障已经broken了,则抛出异常 会导致状态异常
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)// 如果进入下一个纪元了,则返回剩余未进入等待状态的线程数
return index;
//超时了关闭屏障
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
//释放锁
lock.unlock();
}
}
//关闭屏障 打断状态
private void breakBarrier() {
generation.broken = true; // 设置broken标识
count = parties; // 重置计数器
trip.signalAll(); // 唤醒所有阻塞线程
}
// 开启下一代,重置所有参数
private void nextGeneration() {
// signal completion of last generation
trip.signalAll(); // 唤醒所有等待的线程
// set up next generation
count = parties; // 计数器重置
generation = new Generation(); // 重新实例化 纪元
}
使用场景:
等所有线程到达栅栏后,在往下执行
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
class MyThread extends Thread {
private CyclicBarrier cb;
public MyThread(String name, CyclicBarrier cb) {
super(name);
this.cb = cb;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " going to await");
try {
cb.await();
System.out.println(Thread.currentThread().getName() + " continue");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class CyclicBarrierDemo {
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
CyclicBarrier cb = new CyclicBarrier(3, new Thread("barrierAction") {
public void run() {
System.out.println(Thread.currentThread().getName() + " barrier action");
}
});
MyThread t1 = new MyThread("t1", cb);
MyThread t2 = new MyThread("t2", cb);
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName() + " going to await");
cb.await();
System.out.println(Thread.currentThread().getName() + " continue");
}
}
运行结果:
t2 going to await main going to await t1 going to await t1 barrier action t1 continue t2 continue main continue
从运行结果可以看出,当所有线程到达await之后,所有线程就继续进行后续continue的操作。
CyclicBarrier 和 CountDownLatch差别:
CountDownLatch和CyclicBarrier都能实现线程之间的等待,但是它们的侧重点不同:
CountDownLatch一般用于某个线程等待其他线程执行完后,它才执行;
CyclicBarrier一般用于一组线程互相等待至某个状态,然后这组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
3、Semaphore(信号量)
Semaphore通过使用计数器来控制对共享资源的访问。 如果计数器大于0,则允许访问。 如果为0,则拒绝访问,
在并发编程中,可以控制返访问同步代码的线程数量
Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略。
Semaphore内部类实现AQS。
Semaphore主要方法
//从信号量获取一个许可,如果无可用许可前将一直阻塞等待,
void acquire()
//获取指定数目的许可,如果无可用许可前也将会一直阻塞等待
void acquire(int permits)
//从信号量尝试获取一个许可,如果无可用许可,直接返回false,不会阻塞
boolean tryAcquire()
//尝试获取指定数目的许可,如果无可用许可直接返回false
boolean tryAcquire(int permits)
//在指定的时间内尝试从信号量中获取许可,如果在指定的时间内获取成功,返回true,否则返回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit)
//释放一个许可,别忘了在finally中使用,注意:多次调用该方法,会使信号量的许可数增加,达到动态扩展的效果,如:初始permits为1,调用了两次release,最大许可会改变为2
void release()
//获取当前信号量可用的许可
int availablePermits()
源码分析:
//信号数量以及是否公平锁
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
acquire方法:
可以看到和CountDownLatch一样用的是AQS的acquireSharedInterruptibly方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly()
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) //判断是否发生中断
throw new InterruptedException();
// 尝试获取锁,如果失败则排队
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
- doAcquireSharedInterruptibly(arg) 调用tryAcquireShared()方法尝试获取信号量。
- 如果没有可用信号,将当前线程加入等待队列并挂起
tryAcquireShared()方法被Semaphore的内部类NonfairSync和FairSync重写,实现有一些区别。
FairSync.tryAcquireShared(int acquires) :
先调用hasQueuedPredecessors()方法,判断队列中是否有等待线程。如果有,直接返回-1,表示没有可用信号
队列中没有等待线程,再使用CAS尝试更新state,获取信号
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
NofairSync.tryAcquireShared(int acquires):
非公平锁对于信号的获取是直接使用CAS进行尝试的。
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
release 方法:
-
释放一个信号
-
release方法间接的调用了Sync的tryReleaseShared方法,该方法基于cas 将state的值设置为state+1,一直循环确保CAS操作成功,成功后返回true。
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
使用场景:
4个人一起去上厕所,不过厕所只有2个位置,不能四个人一起用,这种场景就可以用到Semaphore。
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) throws InterruptedException {
//厕所里只有两个坑位,初始化信号量总数为2。
Semaphore washbasin = new Semaphore(2);
List<Thread> threads = new ArrayList<>(4);
threads.add(new Thread(new People(washbasin, "二筒")));
threads.add(new Thread(new People(washbasin, "小鸡")));
threads.add(new Thread(new People(washbasin, "八万")));
threads.add(new Thread(new People(washbasin, "三条")));
for (Thread thread : threads) {
thread.start();
Thread.sleep(50);
}
for (Thread thread : threads) {
thread.join();
}
}
}
class People implements Runnable {
private Semaphore washroom;
private String name;
public People(Semaphore washroom, String name) {
this.washroom = washroom;
this.name = name;
}
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
Random random = new Random();
washroom.acquire();
System.out.println(
sdf.format(new Date()) + " " + name + " 开始蹲坑了");
Thread.sleep((long) (random.nextDouble() * 2000) + 3000);
System.out.println(
sdf.format(new Date()) + " " + name + " 蹲坑结束!");
washroom.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
14:24:43.235 小鸡 开始蹲坑了
14:24:43.235 二筒 开始蹲坑了
14:24:46.847 小鸡 蹲坑结束!
14:24:46.849 八万 开始蹲坑了
14:24:47.492 二筒 蹲坑结束!
14:24:47.492 三条 开始蹲坑了
14:24:50.188 八万 蹲坑结束!
14:24:50.744 三条 蹲坑结束!