多线程编程
- 线程的常见操作。创建,运行,暂停,销毁,让步,停止,中断信号,等待,唤醒等
- 多线程数据竞争问题,锁:sync关键字,Lock
- 多线程“流程控制”,同步点(如Semaphore)
- 多线程数据交换与共享
种类
- Semaphore:信号量
- CountDownLatch:“倒计数器”
- CyclicBarrier:循环屏障,篱栅
- Phaser:阶段转移器
- Exchanger:交换器
Semaphore
作用
Semaphore:信号量,提供了资源数量的并发访问控制。
ReentrantLock可重入锁使得资源仅能被一个线程访问,Semaphore可以允许不超过指定数量的线程共同访问。
API使用
// 一开始有5份共享资源。第二个参数表示是否是公平
Semaphore myResources = new Semaphore(5, true);
// 工作线程每获取一份资源,就在该对象上记下来
// 在获取的时候是按照公平的方式还是非公平的方式,就要看上一行代码的第二个参数了。
// 一般非公平抢占效率较高。
// 超过限制数量5则会阻塞,直到获取到资源
myResources.acquire();
// 工作线程每归还一份资源,就在该对象上记下来
// 此时资源可以被其他线程使用
myResources.release();
/*
释放指定数目的许可,并将它们归还给信标。
可用许可数加上该指定数目。
如果线程需要获取N个许可,在有N个许可可用之前,该线程阻塞。
案例:
大学生到自习室抢座,写作业:
如果线程获取了N个许可,还有可用的许可,则依次将这些许可赋予等待获取许可的其他线程。
*/
semaphore.release(2);
/*
从信标获取指定数目的许可。如果可用许可数目不够,则线程阻塞,直到被中断。
该方法效果与循环相同,
for (int i = 0; i < permits; i++) acquire();
只不过该方法是原子操作。
如果可用许可数不够,则当前线程阻塞,直到:(二选一)
1. 如果其他线程释放了许可,并且可用的许可数满足当前线程的请求数字;
2. 其他线程中断了当前线程。
permits – 要获取的许可数
*/
semaphore.acquire(3);
实现
类图
通过类图可以发现其结构和ReentrantLock及其相似
CountDownLatch
作用
计数器,控制线程的执行顺序。一般用于主线程等待子线程执行完毕后继续执行。
API使用
//5是倒计数值
CountDownLatch latch = new CountDownLatch(5);
//子线程执行完毕后调用countDown()方法,计数减1
latch.countDown();
//await()方法阻塞当前线程,直到计数器归零,唤醒本线程,继续执行
latch.await();
//其他业务代码
实现
类图
同样基于AQS实现
CyclicBarrier
作用
和CountDownLatch类似但是更强大。CountDownLatch可以使多个线程同步一次,CyclicBarrier可以同步多次。
API使用
CyclicBarrier barrier = new CyclicBarrier(5);
//子线程业务代码块1
//子线程同步点1,阻塞本线程,直到计数器归零后,才唤起本线程
barrier.await();
//子线程业务代码块2
//子线程同步点2,阻塞本线程,直到计数器归零后,才唤起本线程
barrier.await();
//...
原理
- 使用倒计数器
- 使用ReentrantLock加锁,Condition trip 实现线程的阻塞和唤醒
public class CyclicBarrier {
// 可重入锁
private final ReentrantLock lock = new ReentrantLock();
// 用于线程之间相互唤醒
private final Condition trip = lock.newCondition();
// 线程总数
private final int parties;
// 计数器
private int count;
// 世代,即同步点
private Generation generation = new Generation();
// ...
// 构造方法
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
// 参与方数量
this.parties = parties;
this.count = parties;
// 当所有线程被唤醒时,执行barrierCommand表示的Runnable。
this.barrierCommand = barrierAction;
}
}
public CyclicBarrier(int parties) {
this(parties, null);
}
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
// 首先检查屏障是否破损,即其他线程调用了breakBarrier,例如:
// 1. 其他线程响应中断
// 2. barrierAction执行异常
// 3. 执行超时
// 若破损则抛出异常BrokenBarrierException
if (g.broken)
throw new BrokenBarrierException();
// 响应中断,如:接到其他线程的中断信号
// 本线程抛出中断异常,其他阻塞的线程被唤醒抛出屏障破损
if (Thread.interrupted()) {
// 打破屏障,本世代重新倒计数,唤醒所有阻塞的线程
breakBarrier();
// 抛出中断异常
throw new InterruptedException();
}
// 每个线程调用一次await(),count都要减1
int index = --count;
// 当count减到0的时候,此线程唤醒其他所有线程
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//开启下个世代,唤起所有线程,重新倒计数
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 循环执行,直到同步点,屏障破损,中断信号或者超时
// loop until 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();
}
}
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;
count = parties;
trip.signalAll();
}
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
Phaser
作用
功能比CyclicBarrier和CountDownLatch更加强大
API使用
Phaser phaser = new Phaser(5);
// 替代CountDownLatch
// arrive:标记为已到达,即计数器减一
phaser.arrive();
// 同步点等待,阻塞,直到计数器归零
phaser.awaitAdvance(phaser.getPhase());
// 替代CyclicBarrier
// 到达同步点并等待,即计数器减一并阻塞,直到计数器归零
phaser.arriveAndAwaitAdvance();
// 新特性之一:动态调整线程个数
register() // 注册一个
bulkRegister(int parties) // 注册多个
arriveAndDeregister() // 解除注册
// 新特性之二:层次Phaser
Phaser root = new Phaser(2);
Phaser c1 = new Phaser(root, 3);
Phaser c2 = new Phaser(root, 2);
Phaser c3 = new Phaser(c1, 0);
// 形成了如下图的树状Phaser
/*
本来root有两个参与者,然后为其加入了两个子Phaser(c1,c2),每个子Phaser会算作1个参与者,root的参与者就变成2+2=4个。
c1本来有3个参与者,为其加入了一个子Phaser c3,参与者数量变成3+1=4个。
c3的参与者初始为0,后续可以通过调用register()方法加入。
对于树状Phaser上的每个节点来说,可以当作一个独立的Phaser来看待,其运作机制和一个单独的Phaser是一样的。
父Phaser并不用感知子Phaser的存在,当子Phaser中注册的参与者数量大于0时,会把自己向父节点注册。
当子Phaser中注册的参与者数量等于0时,会自动向父节点解除注册。父Phaser把子Phaser当作一个正常参与的线程就即可。
*/
原理
Phaser没有基于AQS来实现,但具备AQS的核心特性:state变量、CAS操作、阻塞队列。先从state变量说起。
Exchanger
作用
用于两个线程之间交换数据
API使用
Exchanger<String> exchanger = new Exchanger<>();
// 如果没有其他线程调用exchange,线程阻塞,直到有其他线程调用exchange为止,
String otherData = exchanger.exchange("交换数据1");