问题描述
给定三个线程,分别命名为A、B、C,要求这三个线程按照顺序交替打印ABC,每个字母打印10次,最终输出结果为:ABCABCABCABCABCABCABCABCABCABC。
使用 ReentrantLock 解题
首先分析要素,要有三个线程,多线程打印要做同步,需要有一个锁,多线程实现使用Thread,直接开干。
第一版
非常快速的手写一版框架出来,先运行一下看看效果。
public class Main {
public static void main(String[] args) throws InterruptedException {
// 分析 思路是分别输出,要保证线程有序输出
ReentrantLock lock = new ReentrantLock();
Condition ac = lock.newCondition();
Condition bc = lock.newCondition();
Condition cc = lock.newCondition();
Thread a = new Thread(() -> {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
System.out.print("A");
bc.signal();
ac.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
});
Thread b = new Thread(() -> {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
System.out.print("B");
cc.signal();
bc.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
});
Thread c = new Thread(() -> {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
System.out.print("C");
ac.signal();
cc.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
});
a.start();
b.start();
c.start();
a.join();
b.join();
c.join();
}
}
运行得到结果:
ABCABCABCABCABCABCABCABCABCABCA
貌似没有问题。好像主线程没有退出,一直处于运行状态。
如果调整一下线程的启动顺序再执行呢?
b.start();
a.start();
c.start();
运行得到结果:
BACBACBACBACBACBACBACBACBACBACA
发现打印结果竟然跟启动顺序有关系,这肯定不符合预期!
在多线程场景下,启动的顺序不一定跟执行顺序有关,所以这里要强制介入,保证abc线程的顺序执行。顺便解决主线程未退出的问题。
第二版
执行顺序,可以增加共享变量,通过在线程执行时增加条件判断,来控制输出。
主线程未退出,主要是因为子线程await后没有被唤醒,子线程不退出,所以主线程未退出,通过增加结束条件来解决。
public class Main {
public static void main(String[] args) throws InterruptedException {
// 分析 思路是分别输出,要保证线程有序输出
ReentrantLock lock = new ReentrantLock();
Condition ac = lock.newCondition();
Condition bc = lock.newCondition();
Condition cc = lock.newCondition();
AtomicInteger count = new AtomicInteger(0);
int max = 30;
Thread a = new Thread(() -> {
while (true) {
lock.lock();
try {
// 控制当前打印的条件,加的原因是不保证线程的启动顺序就是执行顺序
if (count.get() % 3 != 0) {
ac.await();
}
// 保证程序能结束
if (count.get() >= max) {
bc.signal();
return;
}
System.out.print("A");
bc.signal();
count.incrementAndGet();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
});
Thread b = new Thread(() -> {
while (true) {
lock.lock();
try {
if (count.get() % 3 != 1) {
bc.await();
}
if (count.get() >= max) {
cc.signal();
return;
}
System.out.print("B");
cc.signal();
count.incrementAndGet();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
});
Thread c = new Thread(() -> {
while (true) {
lock.lock();
try {
if (count.get() % 3 != 2) {
cc.await();
}
if (count.get() >= max) {
ac.signal();
return;
}
System.out.print("C");
ac.signal();
count.incrementAndGet();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
});
b.start();
a.start();
c.start();
a.join();
b.join();
c.join();
}
}
运行得到结果:
ABCABCABCABCABCABCABCABCABCABCA
结果符合预期,线程也能正确关闭。就是代码有些丑,大部分代码的逻辑都是重复的,可以考虑提取优化。
第三版
抽取公共逻辑代码,可拓展至ABCD。。。
public class Main {
public static void main(String[] args) throws InterruptedException {
// 分析 思路是分别输出,要保证线程有序输出
ReentrantLock lock = new ReentrantLock();
AtomicInteger count = new AtomicInteger(0);
int times = 10; // 循环次数
int total = 4; // 线程数
List<Condition> conditions = new ArrayList<>();
for (int i = 0; i < total; i++) {
conditions.add(lock.newCondition());
}
Thread a = new Thread(new Printer(lock, conditions, count, total * times, 0, total, "A"));
Thread b = new Thread(new Printer(lock, conditions, count, total * times, 1, total, "B"));
Thread c = new Thread(new Printer(lock, conditions, count, total * times, 2, total, "C"));
Thread d = new Thread(new Printer(lock, conditions, count, total * times, 3, total, "D"));
b.start();
a.start();
c.start();
d.start();
a.join();
b.join();
c.join();
d.join();
}
static class Printer implements Runnable {
private ReentrantLock lock;
private List<Condition> conditions;
private AtomicInteger count;
private int max;
private int index;
private int total;
private String print;
public Printer(ReentrantLock lock, List<Condition> conditions, AtomicInteger count, int max, int index, int total, String print) {
this.lock = lock;
this.conditions = conditions;
this.count = count;
this.max = max;
this.index = index;
this.total = total;
this.print = print;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
if (count.get() % total != index) {
conditions.get(index).await(); // 当前线程休眠
}
// 保证程序能结束
if (count.get() >= max) {
conditions.get((index + 1) % total).signal(); // 唤醒下个线程
return;
}
System.out.print(print);
conditions.get((index + 1) % total).signal();
count.incrementAndGet();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
}
}