字节跳动基础架构岗的终面问题:
有三个线程,分别能够打出A,B,C三个字母,如何让它们并发协作的打出ABCABCABC…这样的字符串?(仅使用ReentrantLock的功能)
使用一个lock与对应创建的condition,思路如下:
那么通俗来讲,流程就变成三个人不断抢锁,抢到锁后发现没轮到自己干活的时候,就先去睡觉。成功抢到锁并打印内容后,把其它线程叫醒并继续抢锁。直到所有人都输出了指定数量的字母。
public class AbcThread extends Thread {
private final int index;
private final Lock lock;
private final Condition cond;
private static volatile int cnt = 0;
private static final char[] CHARS = {'A','B','C'};
private static final int SIZE = 100;
public AbcThread(int index, Lock lock, Condition cond) {
this.index = index;
this.lock = lock;
this.cond = cond;
}
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
lock.lock();
try {
if (AbcThread.cnt % 3 != index) {
cond.await();
}
System.out.print(CHARS[index]);
System.out.flush();
AbcThread.cnt++;
cond.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String args[]) {
Lock lock = new ReentrantLock(true);
Condition condition = lock.newCondition();
for (int i=0; i<3; i++) {
new AbcThread(i, lock, condition).start();
}
}
}
改进思路:
创建三个condition,每一个condition只唤醒下一个condition,其余功能保持不变。这样似乎可以减少抢锁后再反复沉睡的损耗——但如果我们使用的是公平锁,则优化后的区别并不大。
原因是公平锁本身就会在抢不到锁时进入排队等待,而我们是保持顺序执行各线程的,也就意味排队也有很大几率是按顺序的。
对于这类无法优化性能的题目,通常都暗含着多个线程的执行本身就不是并发的,它们通常只能进行一种操作,并争抢同一把锁。(因此我们需要一个lock)另一方面争取同一把锁的顺序可能不合预期,需要把不合预期的线程暂时block住,并在必要时需要唤醒。(因此我们需要一个condition)
思路一之所以设计复杂,主要是因为三个线程抢一把锁时,顺序难以控制。如果我们使用三把“锁”,每一次只打开一把锁做任务,完成后关闭当前锁(堵塞当前进程)并打开下一把锁(释放下一个线程),就可以圆满的完成任务了。
那为什么不用三个ReentrantLock来实现呢?因为持有lock的线程,想要堵塞住自己又在后续被别人唤醒,就又回到用condition的设计了,并且还用了三个锁!(思路一中仅用单Lock、单Condition也能实现)
Semaphore(信号量)可以设置初始的锁的个数,每个线程可以随意的从信号量中取放任意数量的锁。当信号量中不存在锁时取锁会堵塞,并等到有线程把锁放进来时再拉起。
因为本题可以使用三个信号量分发给三个线程,初始时只有A线程的信号量有锁,因此A线程取走锁并打印后,激活了B线程的信号量;随后B线程也做相同的事,并激活C线程的信号量……
public class SemaphoreThread extends Thread {
private static int SIZE = 1000;
private static final char[] CHARS = {'A','B','C'};
private final int index;
private final Semaphore current;
private final Semaphore next;
public SemaphoreThread(int index, Semaphore current, Semaphore next) {
this.index = index;
this.current = current;
this.next = next;
}
public static void main(String[] args) {
Semaphore[] semaphores = {
new Semaphore(1), new Semaphore(0), new Semaphore(0)
};
for (int i=0; i<3; i++) {
new SemaphoreThread(i, semaphores[i], semaphores[(i + 1) % 3]).start();
}
}
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
try {
current.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(CHARS[index]);
next.release();
}
}
}