「这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战」
本文被《从小工到专家的 Java 进阶之旅》收录。
你好,我是看山。
来个面试题,让大家练练手。这个题在阿里和小米都被问过,所以放在这个抛砖引玉,期望能够得到一个更佳的答案。
实现 3 个线程 A、B、C,A 线程持续打印“A”,B 线程持续打印“B”,C 线程持续打印“C”,启动顺序是线程 C、线程 B、线程 A,打印的结果是:ABC。
解法一:状态位变量控制
这个问题考察的是多线程协同顺序执行。也就是第一个线程最先达到执行条件,开始执行,执行完之后,第二个线程达到执行条件,开始执行,以此类推。可以想到的是,通过状态位来表示线程执行的条件,多个线程自旋等待状态位变化。
线上代码:
1 | java复制代码import java.util.concurrent.locks.Lock; |
可以看到,状态位state
使用volatile
修饰,是希望一个线程修改状态位值之后,其他线程可以读取到刚修改的数据,这个属于 Java 内存模型的范围,后续会有单独的章节描述。
这个可以解题,但是却有很多性能上的损耗。因为每个进程都在自旋检查状态值state
是否符合条件,而且自旋过程中会有获取锁的过程,代码中在不符合条件时打印了一些内容,比如:System.out.println("A thread & state = " + state);
,我们可以运行一下看看结果:
1 | ini复制代码C thread & state = 0 |
可以看到,在A线程获取到锁之前,C线程和B线程自旋了100多次,然后A线程才获取机会获取锁和打印。然后在B线程获取锁之前,C线程又自旋了53次。性能损耗可见一斑。
解法二:Condition实现条件判断
既然无条件自旋浪费性能,那就加上条件自旋。
代码如下:
1 | java复制代码import java.util.concurrent.locks.Condition; |
通过Lock
锁的Condition
实现有条件自旋,运行结果如下:
1 | vbnet复制代码C await start |
可以从运行结果看到,C线程发现自己不符合要求,就通过conditionC.await();
释放锁,然后等待条件被唤醒后重新获得锁。然后是B线程,最后是A线程开始执行,发现符合条件,直接运行,然后唤醒B线程的锁条件,依次类推。这种方式其实和信号量很类似。
解法三:信号量
先上代码:
1 | java复制代码import java.util.concurrent.Semaphore; |
代码中执行前先执行了semaphoreB.acquire();
和semaphoreC.acquire();
,是为了将B和C的信号释放,这个时候,就能够阻塞B线程、C线程中信号量的获取,直到顺序获取了信号值。
文末总结
这个题是考察大家对线程执行顺序和线程之间协同的理解,文中所实现的三种方式,都能解题,只不过代码复杂度和性能有差异。因为其中涉及很多多线程的内容,后续会单独开文说明每个知识点。
推荐阅读
- Java 并发基础(一):synchronized 锁同步
- Java 并发基础(二):主线程等待子线程结束
- Java 并发基础(三):再谈 CountDownLatch
- Java 并发基础(四):再谈 CyclicBarrier
- Java 并发基础(五):面试实战之多线程顺序打印
你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。欢迎关注公众号「看山的小屋」,发现不一样的世界。
本文转载自: 掘金