多线程交替打印
- 前言
- 一、交替打印
- 二、八种线程先后控制
- 1、ReentrankLock + 等待与唤醒
- 2、synchronized + 等待与唤醒
- 3、自旋+让出cpu
- 4、cyclicBarrier
- 5、semaphore
- 6、synchronousQueue阻塞队列
- 7、BlockingQueue
- 8、LockSupport
- 总结
- 参考文献
前言
线程并发问题需要合理的控制机制,如并发交替打印,可等待与唤醒,可自旋,可阻塞自己相互解锁。
一、交替打印
二、八种线程先后控制
1、ReentrankLock + 等待与唤醒
// 交替打印。
public class FooBar {
/*
reentrantLock + condition + 通知signal和等待await。
ReentrantLock简介:利用CAS(原子操作/CPU指令) + AQS队列实现,支持公平和非公平锁。
ReentrantLock和synchronized的区别,
1-相同点:独占锁(对于临界区);可重入锁(递归场景);
2-区别:synchronized解锁隐私,JVM层面,简单方便,应用简单的并发场景;reentrantLock手动加锁解锁,复杂灵活,适用于复杂并发场景,且API层面,可操控性强。
*/
// 可重入锁
ReentrantLock rk = new ReentrantLock();
// 因该锁而阻塞的条件对象。
private final Condition c = rk.newCondition();
// 设置可见的标志位。
private volatile boolean flag = true;
private int n;
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
rk.lock();
try {
// printFoo.run() outputs "foo". Do not change or remove this line.
while (!flag) c.await();
printFoo.run();
flag = !flag;
c.signal();
} finally {
rk.unlock();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
rk.lock();
try {
// printBar.run() outputs "bar". Do not change or remove this line.
while (flag) c.await();
printBar.run();
flag = !flag;
c.signal();
} finally {
rk.unlock();
}
}
}
}
2、synchronized + 等待与唤醒
class FooBar3 {
/*
idea3:synchronized + 等待与唤醒
*/
// 设置可见的标志位。
private volatile boolean flag = true;
// 对象锁
private final Object lock = new Object();
private int n;
public FooBar3(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (lock) {
// printFoo.run() outputs "foo". Do not change or remove this line.
while (!flag) lock.wait();
printFoo.run();
flag = false;
lock.notifyAll();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (lock) {
// printBar.run() outputs "bar". Do not change or remove this line.
while (flag) lock.wait();
printBar.run();
flag = true;
// 唤醒
lock.notifyAll();
}
}
}
}
3、自旋+让出cpu
class FooBar2 {
/*
idea2:自旋(timeout) + 让出CPU(yield)
*/
// 设置可见的标志位。
private volatile boolean flag = true;
private int n;
public FooBar2(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; ) {
// printFoo.run() outputs "foo". Do not change or remove this line.
if (flag) {
printFoo.run();
flag = false;
++i;
} else Thread.currentThread().yield();
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; ) {
// printBar.run() outputs "bar". Do not change or remove this line.
if (!flag) {
printBar.run();
flag = true;
++i;
} else Thread.currentThread().yield();// 让执行态 -> 就绪态
}
}
}
4、cyclicBarrier
class FooBar4 {
/*
idea4:cyclicBarrier同步点等待 + 自旋
cyclicBarrier和countdownLatch的区别:
1-countdownLatch参与的线程职责不单一,有的在倒计时,有的在等待倒计时结束;cyclicBarrier参与的线程职责单一,都是到达一个同步点。
2-cyclicBarrier顾名思义,是循环使用,当线程全部达到同步点时,开启下一轮同步点等待。
*/
// 设置可见的标志位。
private volatile boolean flag = true;
// 循环栅栏。
CyclicBarrier cb = new CyclicBarrier(2);
private int n;
public FooBar4(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo". Do not change or remove this line.
// 空等
while (!flag) ;
printFoo.run();
flag = false;
// 到达同步点
try {
cb.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
// 先达到同步点。
try {
cb.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
// printBar.run() outputs "bar". Do not change or remove this line.
while (flag) ;
printBar.run();
flag = true;
}
}
}
5、semaphore
class FooBar5 {
/*
idea5:信号量
*/
// 信号量
Semaphore fs = new Semaphore(1);
Semaphore bs = new Semaphore(0);
private int n;
public FooBar5(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo". Do not change or remove this line.
fs.acquire();
printFoo.run();
bs.release();
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printBar.run() outputs "bar". Do not change or remove this line.
bs.acquire();
printBar.run();
fs.release();
}
}
}
6、synchronousQueue阻塞队列
class FooBar6 {
/*
idea6:synchronousQueue阻塞队列,靠着线程循环阻塞与不阻塞其他线程来推进交替打印。
*/
// 阻塞队列,不存储元素,一个put必须先等一个take,Executors.newCachedThreadPool()就使用了SynchronousQueue
SynchronousQueue<Integer> start = new SynchronousQueue<>();
SynchronousQueue<Integer> end = new SynchronousQueue<>();
private int n;
public FooBar6(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
end.take(); // 自己执行完,给bar解锁。
start.put(1); // 然后把自己阻塞,让已经被解锁的bar来给我解锁。
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printBar.run() outputs "bar". Do not change or remove this line.
end.put(1);// 阻塞自己,直到foo给我解锁。
printBar.run();
start.take();// 自己执行完,给foo解锁。
}
}
}
7、BlockingQueue
class FooBar7 {
/*
idea7:阻塞队列
*/
// 双阻塞队列,自我上锁 + 上锁前的互相帮助。
// 一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半
private BlockingQueue<Integer> fbq = new LinkedBlockingDeque<>(1);
private BlockingQueue<Integer> bbq = new LinkedBlockingDeque<>(1);
private int n;
public FooBar7(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo". Do not change or remove this line.
fbq.put(1); // 先put,再循环put时超过容量,就阻塞了。
printFoo.run();
bbq.put(1); // 让 未来给我解锁的人能够take()不阻塞。
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printBar.run() outputs "bar". Do not change or remove this line.
bbq.take(); // 有人解我锁,让我能够take(),顺便循环时把自己阻塞。
printBar.run();
fbq.take(); // 别人帮了我,我也要帮别人take(),免得put时超过容量就阻塞。
}
}
}
8、LockSupport
class FooBar8 {
/*
idea8:LockSupport,两个静态方法,阻塞当前线程和唤醒指定线程,park()/unpark()
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,
可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
初始时,permit为0,当调用unpark()方法时,线程的permit加1,当调用park()方法时,如果permit为0,则调用线程进入阻塞状态。
*/
Map<String, Thread> fx = new ConcurrentHashMap<>();
volatile boolean flag = true;
private int n;
public FooBar8(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
fx.put("foo", Thread.currentThread());
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo". Do not change or remove this line.
while (!flag) LockSupport.park();
printFoo.run();
flag = !flag;
LockSupport.unpark(fx.get("bar"));
}
}
public void bar(Runnable printBar) throws InterruptedException {
fx.put("bar", Thread.currentThread());
for (int i = 0; i < n; i++) {
// printBar.run() outputs "bar". Do not change or remove this line.
while (flag) LockSupport.park();
printBar.run();
flag = !flag;
LockSupport.unpark(fx.get("foo"));
}
}
}