三个线程交叉打印ABC(synchronized、wait/notify)

三个线程交叉打印ABC(synchronized、wait/notify)

  这是以前在面试美团Java实习生岗位时被问到的一道题,当时没有写出来,后面在网上看别人代码也是云里雾里,感觉每个人写的也都不一样,没有强调重点,导致看的越多就感觉越乱,有人用一把锁,也有人用三把锁。这次我尽力把交叉打印讲清楚,把重点都标注出来。

  首先,线程打印字母是线程在执行一个任务,所以我们需要定义三个任务(Runnable),然后交给三个线程去执行(Thread)。

  我们要明白三个线程交叉打印,这三个线程之间时需要相互通信的,也就是说线程1打印完A之后,需要通知线程2去打印B,线程2打印完B之后,需要通知线程3去打印C,以此类推。好了,我们现在知道线程之间是要相互通信的,那么线程之间要怎么通信呢?可共享变量是Java给我提供的天然的通信媒介,线程可以通过读取共享变量的内容,然后线程本身来决定在线程中进行什么操作。

  这个共享变量就是作为锁对象,只有获取到了锁对象才可以执行任务中的逻辑,否则任务会被阻塞。

  由于代码在执行的过程中会出现原子性、可见性、指令重排等情况,所以Java提供了如synchronized关键字来保证线程通信时的正确性。如果不清楚原子性、可见性、指令重排也没有关系,只要清楚synchronized可以保证线程之间在通信时的正确性就行了,synchronized的作用就是加锁。

  wait/notify都是对于锁对象的操作,不是执行任务的线程来进行wait/notify,这一点非常重要!!!锁对象的作用是持有锁对象的线程才可以进行打印,如果持有锁对象的线程不满足打印条件(条件在代码中有说明),那么就需要让出锁对象,让出锁对象的操作就是wait,然后通知其他线程来竞争锁对象,通知的操作就是notify,记住wait/notify都是由锁对象来执行的!!!

  好了,我们现在明白我们需要干什么了:定义三个任务,然后交给三个线程去执行、使用一个共享变量来在线程中进行通信、用synchronized来保证线程通信时的正确性

  接下来,我们来看代码:

public class Test07 {

    //定义一个共享变量,用来在线程中进行通信
    private static final Object obj = new Object();

    //定义一个变量来记录打印的次数,控制打印条件
    private static int count = 1;

    public static void main(String[] args) {
        //创建三个线程,然后把三个任务分别放入这三个线程执行

        //创建线程1执行任务1
        new Thread(new Task1()).start();
        //创建线程2执行任务2
        new Thread(new Task2()).start();
        //创建线程3执行任务4
        new Thread(new Task3()).start();
    }

    //任务1
    private static class Task1 implements Runnable {

        @Override
        public void run() {
            synchronized (obj) {
                //打印十次A
                for (int i = 0; i < 10; i++) {
                    //一直轮询,如果条件不是要打印A的条件,那么直接释放锁
                    /*
                        这个地方一定要用while,如果用了if的话,再下次重新获得锁的时候,是继续往下去执行,走到obj.notifyAll()这一行代码,不会再判断count的值,
                        用while的话会再去判断count的值,如果符合条件再往下执行,走到obj.notifyAll()这一行代码,可以自己把while换成if试试,看看结果
                     */
                    while (count % 3 != 1) {
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //通知其他所有线程可以来抢占锁了,但是发现现在线程1在持有锁,其他线程还抢不到,只有等到线程1释放锁之后,才可以抢到锁
                    obj.notifyAll();
                    System.out.println("A");
                    count++;
                }
            }
        }
    }

    //任务2
    private static class Task2 implements Runnable {

        @Override
        public void run() {
            synchronized (obj) {
                //打印十次B
                for (int i = 0; i < 10; i++) {
                    //一直轮询,如果条件不是要打印B的条件,那么直接释放锁
                    while (count % 3 != 2) {
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //通知其他所有线程可以来抢占锁了,但是发现现在线程2在持有锁,其他线程还抢不到,只有等到线程2释放锁之后,才可以抢到锁
                    obj.notifyAll();
                    System.out.println("B");
                    count++;
                }
            }
        }
    }

    //任务3
    private static class Task3 implements Runnable {

        @Override
        public void run() {
            synchronized (obj) {
                //打印十次C
                for (int i = 0; i < 10; i++) {
                    //一直轮询,如果条件不是要打印C的条件,那么直接释放锁
                    while (count % 3 != 0) {
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //通知其他所有线程可以来抢占锁了,但是发现现在线程3在持有锁,其他线程还抢不到,只有等到线程3释放锁之后,才可以抢到锁
                    obj.notifyAll();
                    System.out.println("C");
                    count++;
                }
            }
        }
    }
}
posted @ 2020-09-25 11:18  饶一一  阅读(910)  评论(0编辑  收藏  举报