题目:请使用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线程通信。如果有其他的线程通信方式也可以直接套用上面的代码来进行实现。
题外话:笔试题的数量很多,单纯刷是没法刷完的,我们需要调整刷题的策略,当我们在遇到每一道笔试题的时候不要急,慢慢来争取能够吃透它。怎么吃透一道面试题呢?首先我们得改变原有的思维方式,当我们面对一道面试题的时候,今天我想讲的:不是如何去解决问题,而是如何去思考问题,这很关键。有时候我们无法解出一道笔试题,不是我们能力不够,很可能是思维方式不对,即使解出来了,也仅仅是满足当前的答案,没有去思考是否有其他相似的场景可以套用,或者是否有更好的方案去解决问题。
说了这么多,当面对一道笔试题时,应该怎么做:举一反三,追求极致,复盘总结。当面对每个问题的时候,你都能以这种方式思考,随着时间的推移,你的思维能力会越来越强,学习能力也会稳步上升
原文地址:https://blog.csdn.net/Royal_lr/article/details/103422769?utm_source=app