基于synchronized+wait/notify
思路: 打印线程a、b、c… …依次对应一个标志flag,为了简便就从下标0开始,0、1、2、… …;定义一个全局变量flag,所有打印线程都可以访问,约定只有当全局flag与打印线程的标志一致时才可以打印字符,否则调用wait()方法进入等待;打印完之后,将全局flag设置为下一个要打印字符的线程的标志,并唤醒所有等待的打印线程。
代码实现如下。
public class Main {
public static final int THREAD_NUM = 3;
public static void main(String[] args) {
CyclePrint cyclePrint = new CyclePrint(3, 0);
for (int i = 0; i < THREAD_NUM; i++) {
final int curr = i, next = i + 1 < THREAD_NUM ? i + 1 : 0;
new Thread(() -> cyclePrint.print(curr, next, (char) ('a' + curr) + "")).start();
}
}
}
class CyclePrint {
private int loopNum;
private int flag;
public CyclePrint(int loopNum, int flag) {
this.loopNum = loopNum;
this.flag = flag;
}
public void print(int currFlag, int nextFlag, String content) {
for (int i = 0; i < loopNum; i++) {
synchronized (this) {
while (currFlag != this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print(content);
this.flag = nextFlag;
this.notifyAll();
}
}
}
}
运行结果如下,
基于ReentrantLock+Condition
线程1打印a、线程2打印b、线程3打印c、… …,交替打印,即输出的结果为abcabcabc… …
思路: 借助可重入锁ReentrantLock,用它来产生条件变量Condition,约定每个线程对应一个Condition,核心伪代码如下,
loop: {
lock();
currCondition.await();
print();
nextCondition.signal();
unlock();
}
最终的实现代码如下,
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static final int THREAD_NUM = 3;
public static void main(String[] args) throws InterruptedException {
CyclePrint cyclePrint = new CyclePrint(3);
List<Condition> conditions = new ArrayList<>(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
conditions.add(cyclePrint.newCondition());
}
// 引用计数器,目的是为了确保第一个线程能顺利进入阻塞状态,再由主线程唤醒
CountDownLatch cdl = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
final int curr = i, next = i + 1 < THREAD_NUM ? i + 1 : 0;
new Thread(() -> {
cdl.countDown();
cyclePrint.print(conditions.get(curr), conditions.get(next), (char) ('a' + curr) + "");
}).start();
}
cdl.await();
// 如果主线程先执行了signal()方法,而打印线程还没来及阻塞,那么后面所有子线程将一直阻塞
cyclePrint.lock();
conditions.get(0).signal();
cyclePrint.unlock();
}
}
class CyclePrint extends ReentrantLock {
private int loopNum;
public CyclePrint(int loopNum) {
this.loopNum = loopNum;
}
public void print(Condition curr, Condition next, String content) {
for (int i = 0; i < loopNum; i++) {
this.lock();
try {
curr.await();
System.out.print(content);
next.signal();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
this.unlock();
}
}
}
}
运行结果为如下,
基于LockSupport
思路跟上面一样,先让所有打印线程阻塞自己,被唤醒后,打印字符,之后再唤醒下一个打印字符的线程,实现如下,
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;
public class Main {
public static final int THREAD_NUM = 3;
public static List<Thread> threads = new ArrayList<>(THREAD_NUM);
public static void main(String[] args) {
CyclePrint cyclePrint = new CyclePrint(3);
for (int i = 0; i < THREAD_NUM; i++) {
final int curr = i, next = i + 1 < THREAD_NUM ? i + 1 : 0;
threads.add(new Thread(() -> cyclePrint.print(threads.get(next), (char) ('a' + curr) + "")));
}
threads.forEach(thread -> thread.start());
LockSupport.unpark(threads.get(0));
}
}
class CyclePrint {
private int loopNum;
public CyclePrint(int loopNum) {
this.loopNum = loopNum;
}
public void print(Thread next, String content) {
for (int i = 0; i < loopNum; i++) {
LockSupport.park();
System.out.print(content);
LockSupport.unpark(next);
}
}
}
运行结果一样,
LockSupport这个类比较特殊,原理是调用unpark(Thread thread)方法会给关联thread的计数加一,当thread自己执行park()方法时,会检查它的计数是否大于0,大于0便不会阻塞继续往下运行同时计数减一,否则被阻塞。
也就是说主线程可以先对第一个线程执行unpark(Thread t1),让它计数加一,从而保证了打印字符a的线程先打印。