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


    题目:请使用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
  • 相关阅读:
    gradle
    1-NIO使用
    处理非正常终止的错误
    一个取消多生产者单消费者的日志线程池服务
    executes()源码
    死锁
    CyclicBarrier使用
    Semaphore
    Spring学习(4)IOC容器配置bean:定义与实例化
    在Maven上Web项目添加Spring框架
  • 原文地址:https://www.cnblogs.com/jpfss/p/12027328.html
Copyright © 2020-2023  润新知