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

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

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

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

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

public class AlternatePrinting1 implements Runnable {
    private AtomicInteger index=null;
    private int flag=0;
    public AlternatePrinting1(AtomicInteger index,int flag){
        this.index=index;
        this.flag=flag;
    }
    @Override
    public void run() {
        while (index.get()<101){
            if(index.get()%2==flag){
                System.out.println((flag==1?"A:":"B:")+index.get());
                index.getAndIncrement();
            }
        }
    }
}
public class TestQ1 {
    public static void main(String[] args) {
        AtomicInteger index=new AtomicInteger(1);
        Thread A=new Thread(new AlternatePrinting1(index,1));
        Thread B=new Thread(new AlternatePrinting1(index,0));
        A.start();
        B.start();
    }
}

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

第二种:

public class TestQ1Improve {
    static volatile int index = 1;
    public static void main(String[] args) {
        Thread A = new Thread(() -> {
            alternatePrinting(1);
        });
        Thread B = new Thread(() -> {
            alternatePrinting(0);
        });
        A.start();
        B.start();
    }
    public static void alternatePrinting(int flag) {
        while (index < 101) {
            if (index % 2 == flag) {
                System.out.println((flag == 1 ? "A:" : "B:") + index);
                index++;
            }
        }
    }
}

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

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

public class TestQ2 {
    private static volatile int i = 1;
    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    while (i < 101) {
                        System.out.println(Thread.currentThread().getName() + ":" + i++);
                        try {
                            notify();
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    notify();
                }
            }
        };
        Thread A = new Thread(runnable, "A");
        Thread B = new Thread(runnable, "B");
        A.start();
        B.start();
    }
}

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

下面来看第四种

public class TestQ2Extend {
    private static volatile int i = 1;
    public static void main(String[] args){
         Lock lock = new ReentrantLock();
         Condition condition = lock.newCondition();
        Runnable runnable = ()-> {
                try {
                    lock.lock();
                    while (i < 101) {
                        System.out.println(Thread.currentThread().getName() + ":" + i++);
                        condition.signal();
                        try {
                            condition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    condition.signal();
                }finally {
                    lock.unlock();
                }
        };
        Thread A = new Thread(runnable, "A");
        Thread B = new Thread(runnable, "B");
        A.start();
        B.start();
    }
}

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

总结:

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

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

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

 

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

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

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值