多线程之两个线程交替打印的问题分析

场景一 

 在我们面试中经常会有这么一个场景,就是我们用线程A输出“A”字符,有线程B输出“B”字符,交替进行,要求A线程执行完任务输出:“A线程打印完了”,B线程执行完任务输入:“B线程打印完了”,最后有主线程输出一句话“我打印完了”!

当你看到这个场景时,有点多线程经验的人肯定会感觉很容易,然后有可能进行下面的实现,上代码:

//A线程类
public class ThreadA extends Thread {
    private TestThread testThread;
    public ThreadA(TestThread testThread){
        this.testThread=testThread;
    }

    @Override
    public void run(){
        for(int i=0;i<5;i++){
            testThread.printStr("A");
        }
        System.out.println("a线程打印完了");
    }
}
//B线程类
public class ThreadB extends Thread {
    private TestThread testThread;
    public ThreadB(TestThread testThread){
        this.testThread=testThread;
    }
    @Override
    public void run(){
        for(int i=0;i<5;i++){
            testThread.printStr("B");
        }
        System.out.println("b线程打印完了");
    }
}
//测试类
public class TestThread {

    public static  void main(String[] args){
        TestThread testThread=new TestThread();
        ThreadA threadA = new ThreadA(testThread);
        ThreadB threadB = new ThreadB(testThread);
       threadA.setName(
"threadA"); threadB.setName("threadB");      threadB.setPriority(2)//这个目的是将B线程的优先级降低,让A线程先执行,使得在打印的时候先打印“A”,但是我要说但是,在实际测试中你会发现输出的结果并不会像我们预想的一样,具体什么原因造成的我们下面会解释 threadB.start(); threadA.start(); try { threadA.join(); threadB.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("交替打印完成"); } public synchronized void printStr(String str){ String name = Thread.currentThread().getName(); if("A".equals(str)){ System.out.println(name+"-----"+"A"); }else if("B".equals(str)){ System.out.println(name+"-----"+"B"); } try { notify(); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }

上面的代码猛一看很完美,但是如果你真正执行过之后你会发现出现下面的结果,“A”和“B”字符是交替打印了,但是我们想要的三句话,只输出了一句,并且你的idea一直处在运行状态:

 

   其实如果我们仔细分析代码就不难发现为什么程序一直处在没有结束运行的状态,并且只打印了一句话而不是三句话:再出去最后一个threadB-----B的时候结合代码你会发现这是线程B会执行nofity唤醒A线程,然后自己进入wait状态,A线程被唤醒后执行A线程的run方法,但是现在A线程中的run方法的for已经执行晚了,也就是没法进入for循环体了,所以就直接执行了打印语句,这也就是最后一行打印的原因,而因为A线程没有进入for循环体,所以没有调用同步方法printStr,也就没有办法调用里面的notify来唤醒B线程,那么B线程也就没有办法执行打印语句了,一直处在wait状态,也就是B线程一致处在没有结束的状态,那自然而然主线程也不会输出打印语句。

还记得我们在上面的代码中说的一个线程优先级的问题吗?如果你对这段代码执行多次的情况你会发现,第一次打印的可不都是“A”字符,还有可能是“B”,这是为什么呢?

  其实这主要使我们对线程优先级理解的一个误区,对于线程优先级我们通常的理解是具有较高优先级的线程会优先执行,其实这句话的理解应该是,在多线程竞争资源时具有较高优先级的线程会有更大的概率获取资源,也就是说并不是每次都是它先获取资源,而是在大量多次的资源竞争中,它获取到资源的次数会比低优先级的线程多。

说了一些题外话,我们还是要给出正确的交替打印的代码,供大家参考,其实方式很多,这里只是简单列出一种:

其实只需要代码稍微改动就可以解决问题,看代码:

//A线程类
public class ThreadA extends Thread {
    private TestThread testThread;
    public ThreadA(TestThread testThread){
        this.testThread=testThread;
    }

    @Override
    public void run(){
        int count=0;
        for(int i=0;i<5;i++){
            count++;
            testThread.printStr(count,"A");
        }
        System.out.println("a线程打印完了");
    }
}
//B线程类
public class ThreadB extends Thread {
    private TestThread testThread;
    public ThreadB(TestThread testThread){
        this.testThread=testThread;
    }
    @Override
    public void run(){
        int count=0;
        for(int i=0;i<5;i++){
            count++;
            testThread.printStr(count,"B");
        }
        System.out.println("b线程打印完了");
    }
}
//测试类
public class TestThread {
    public static  void main(String[] args){
        TestThread testThread=new TestThread();
        ThreadA threadA = new ThreadA(testThread);
        ThreadB threadB = new ThreadB(testThread);
        threadB.setPriority(1);
        threadA.setName("threadA");
        threadB.setName("threadB");
        threadB.start();
        threadA.start();
        try {
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("交替打印完成");
    }

    public synchronized void printStr(int count,String str){
        String name = Thread.currentThread().getName();
        if("A".equals(str)){
            System.out.println(name+"-----"+"A");

        }else if("B".equals(str)){
            System.out.println(name+"-----"+"B");
        }
        try {
            notify();
            if(count!=5){
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

场景二  

  因为我们上面的交替输出的场景过于简单,我们稍微改变一下,变成对一个字符串'adasdfsafwfvgs'两个线程交替输出字符,最后要求A线程执行完任务输出:“A线程打印完了”,B线程执行完任务输入:“B线程打印完了”,最后有主线程输出一句话“我打印完了”!

@Data
public class TestThread {
    volatile static boolean open=false;
    volatile static int index=0;
    static String s="adasdfsafwfvgs";

    private ThreadA threadA;
    private ThreadB threadB;
    public TestThread(){
        this.threadA=new ThreadA();
        this.threadB=new ThreadB();
    }

    public  class ThreadB extends Thread {
        @Override
        public void run(){
            while(index<s.length()){
                if(open){
                    System.out.println(Thread.currentThread().getName()+"-----"+s.charAt(index++));
                    open=false;
                }
            }
            System.out.println("b线程打印完了");
        }
    }

    public class ThreadA extends Thread {
        @Override
        public void run(){
            while(index< s.length()){
                if(!open){
                    System.out.println(Thread.currentThread().getName()+"-----"+s.charAt(index++));
                    open=true;
                }
            }
            System.out.println("a线程打印完了");
        }
    }

}

测试类:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = KafkaClientApplication.class)
public class TestThreadTest {

    /**
     * 测试使用两个线程对字符串交替输出字符的代码
     */
    @Test
    public void test(){
        TestThread testThread=new TestThread();
        TestThread.ThreadA threadA = testThread.getThreadA();
        TestThread.ThreadB threadB = testThread.getThreadB();
        threadA.setName("threadA");
        threadB.setName("threadB");
        threadA.start();
        threadB.start();
        try {
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我打印完了");
    }
}

测试结果:

 

 

多线程交替打印解决方案

posted @ 2019-10-23 22:06  海棠--依旧  Views(8812)  Comments(2Edit  收藏  举报