白话解析平安笔试题:多线程交替打印

题目:请使用A,B 2个线程,交替打印1-100,提供2种以上的实现方式 ?

说实话,我很懒,没刷过什么多线程相关的题目,和大多数人一样只是知道一些多线程的基础知识,所以第一眼看到这个题目是有点懵的。写这篇文章主要是分享一下我的思考过程,如何通过自己已知的信息,将其整合起来去解决这道问题,这是一种信息整合能力。

现在我们先来剖析一下题目:A、B 2个线程,交替打印。也就是说在同一时间只能有一个线程在执行打印,这2个线程虽然是执行同样的功能代码,但是互斥的。

我们先来看第一种执行方式,直接上代码:

  1. public class AlternatePrinting1 implements Runnable {
  2. private AtomicInteger index=null;
  3. private int flag=0;
  4. public AlternatePrinting1(AtomicInteger index,int flag){
  5. this.index=index;
  6. this.flag=flag;
  7. }
  8. @Override
  9. public void run() {
  10. while (index.get()<101){
  11. if(index.get()%2==flag){
  12. System.out.println((flag==1?"A:":"B:")+index.get());
  13. index.getAndIncrement();
  14. }
  15. }
  16. }
  17. }
  1. public class TestQ1 {
  2. public static void main(String[] args) {
  3. AtomicInteger index=new AtomicInteger(1);
  4. Thread A=new Thread(new AlternatePrinting1(index,1));
  5. Thread B=new Thread(new AlternatePrinting1(index,0));
  6. A.start();
  7. B.start();
  8. }
  9. }

思考过程:最开始我想的是直接弄个for循环打印1-100,然后考虑如何交替,后来发现此路不通,主要是对多线程理解不够。它得交替打印,那变量就得在2个线程间共享,那么我们只需要将打印和自增这2个操作区分成2种情况,让AB分别去执行就行了。那么怎么区分2种情况?先别急,我们先来想下,什么是交替:ABABAB....,比如 A线程打印 1,B线程打印2,A线程打印3....像这样交换执行。所以很容易想到整数分为奇数和偶数,直接用一个变量对2取余结果为0和1 就可以区分这2种情况了。

第二种:

  1. public class TestQ1Improve {
  2. static volatile int index = 1;
  3. public static void main(String[] args) {
  4. Thread A = new Thread(() -> {
  5. alternatePrinting(1);
  6. });
  7. Thread B = new Thread(() -> {
  8. alternatePrinting(0);
  9. });
  10. A.start();
  11. B.start();
  12. }
  13. public static void alternatePrinting(int flag) {
  14. while (index < 101) {
  15. if (index % 2 == flag) {
  16. System.out.println((flag == 1 ? "A:" : "B:") + index);
  17. index++;
  18. }
  19. }
  20. }
  21. }

思考过程:第一种是我最开始想到的实现方式,这种方式和第一种其实差不多,核心都是通过奇偶数来区分2种情况,不同点是:第一种采用的是线程安全的 AtomicInteger 变量,这种是采用的非线程安全的volatile修饰的整型变量。其实是对第一种的优化。首先在上面第一种执行方式的代码中可以看到,A、B2个线程虽然是共享的线程安全的index变量,但是在同一时间 打印index 和 index 的自增是只有一个线程在执行的,只有在while循环判断时,会去共享的读取index变量,所以将其改成采用volatile 的修饰的整型变量即可。

我们继续来看第三种执行方式:

  1. public class TestQ2 {
  2. private static volatile int i = 1;
  3. public static void main(String[] args) throws Exception {
  4. Runnable runnable = new Runnable() {
  5. @Override
  6. public void run() {
  7. synchronized (this) {
  8. while (i < 101) {
  9. System.out.println(Thread.currentThread().getName() + ":" + i++);
  10. try {
  11. notify();
  12. wait();
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. notify();
  18. }
  19. }
  20. };
  21. Thread A = new Thread(runnable, "A");
  22. Thread B = new Thread(runnable, "B");
  23. A.start();
  24. B.start();
  25. }
  26. }

思考过程:这种实现方式的需要考虑的是线程间的通信,这里优先想到的是wait/notify的线程通信方式,所以先用 synchronized将其锁住,然后将A、B 2个线程交替等待唤醒就可以实现交替打印了

下面来看第四种

  1. public class TestQ2Extend {
  2. private static volatile int i = 1;
  3. public static void main(String[] args){
  4. Lock lock = new ReentrantLock();
  5. Condition condition = lock.newCondition();
  6. Runnable runnable = ()-> {
  7. try {
  8. lock.lock();
  9. while (i < 101) {
  10. System.out.println(Thread.currentThread().getName() + ":" + i++);
  11. condition.signal();
  12. try {
  13. condition.await();
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. condition.signal();
  19. }finally {
  20. lock.unlock();
  21. }
  22. };
  23. Thread A = new Thread(runnable, "A");
  24. Thread B = new Thread(runnable, "B");
  25. A.start();
  26. B.start();
  27. }
  28. }

思考过程:这种方式和第三种的核心思路一样,可以说是第三种的扩展实现,都是采用的线程通信的思路来写的,不同点就是这个是采用 Lock 和 Condition的await、signal 方法来实现线程通信,进而达到交替等待唤醒就可以了。

总结:

上面四种实现的线程交替的打印方式可以归纳为2种策略:

1. 自旋:那么什么是自旋:简单来说就是循环等待。第一种和第二种都是采用自旋的方式,实现线程的交替打印,基于这种策略可以有很多种实现。比如可以采用布尔变量的true和false来区分A、B 2个线程,每自增一次就改变一次布尔变量的值即可。

2. 线程通信:第三种和第四种都是采用线程通信的方式实现,第三种是wait/notify 线程通信,第四种是Condition的await/signal线程通信。如果有其他的线程通信方式也可以直接套用上面的代码来进行实现。

 

题外话:笔试题的数量很多,单纯刷是没法刷完的,我们需要调整刷题的策略,当我们在遇到每一道笔试题的时候不要急,慢慢来争取能够吃透它。怎么吃透一道面试题呢?首先我们得改变原有的思维方式,当我们面对一道面试题的时候,今天我想讲的:不是如何去解决问题,而是如何去思考问题,这很关键。有时候我们无法解出一道笔试题,不是我们能力不够,很可能是思维方式不对,即使解出来了,也仅仅是满足当前的答案,没有去思考是否有其他相似的场景可以套用,或者是否有更好的方案去解决问题。

        说了这么多,当面对一道笔试题时,应该怎么做:举一反三,追求极致,复盘总结。当面对每个问题的时候,你都能以这种方式思考,随着时间的推移,你的思维能力会越来越强,学习能力也会稳步上升

原文地址:https://blog.csdn.net/Royal_lr/article/details/103422769?utm_source=app
posted @ 2019-12-12 09:23  星朝  阅读(724)  评论(0编辑  收藏  举报