• 多线程与高并发(11)——经典面试题之实现一个容器,提供两个方法,add,size。


    实现一个容器,提供两个方法,add,size。写两个线程,线程 1 添加 10 个元素到容器中,线程 2 实现监控元素的个数,当个数到 5 个时,线程 2 给出提示并结束。

    1、普通方法

    public class Test_Container {
    
        List list = new ArrayList<>();
        private void add(Object o) {
            list.add(o);
        }
        private Integer size() {
            return list.size();
        }
        
       public static void main(String[] args) {
            Test_Container container = new Test_Container();
    
            new Thread(() -> {
                System.out.println("T1 开始");
                for (int i = 1; i <= 10; i++) {
                    container.add(new Object());
                    System.out.println("add + " + i);
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                }
                System.out.println("T1 结束");
            }, "T1").start();
    
            new Thread(() -> {
                System.out.println("T2 开始");
                while(true){
                    if(container.size()==5){
                        break;
                    }
                }
                System.out.println("T2 结束");
            }, "T2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    赶早不如赶巧,一上来我就运行出了死循环的结果…
    在这里插入图片描述
    这是为什么呢?很简单,因为线程2开始的时候,线程1都执行到7了!
    还有一种情况,如下图:
    在这里插入图片描述
    T2结束的时候,T1都执行到6了。
    我们加volatile关键字尝试一下。

    2、volatile关键字

    public class Test_ContainerWithVolatile {
    
        volatile List list = new ArrayList<>();
        private void add(Object o) {
            list.add(o);
        }
        private Integer size() {
            return list.size();
        }
    
        public static void main(String[] args) {
            Test_ContainerWithVolatile container = new Test_ContainerWithVolatile();
    
            new Thread(() -> {
                System.out.println("T1 开始");
                for (int i = 1; i <= 10; i++) {
                    container.add(new Object());
                    System.out.println("add + " + i);
                
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                }
                System.out.println("T1 结束");
            }, "T1").start();
    
            new Thread(() -> {
                System.out.println("T2 开始");
                while(true){
                    if(container.size()==5){
                        break;
                    }
                }
                System.out.println("T2 结束");
            }, "T2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    看上去没有死循环的问题:
    在这里插入图片描述
    但是,是因为我加了sleep的原因,如果不加sleep,还是不行。
    所以,volatile关键字是不可以的,为什么不可以呢,volatile关键字禁止指令重排序,保证线程可见性,但是又保证不了原子性,更保证不了线程等待不运行。你想读到size=5的时候,他可能都变成6了。
    我们用wait和notify实现尝试一下。

    3、wait和notify实现

    wait()方法的作用是释放锁,加入到等待队列。调用notify或者notifyAll方法不释放锁,只是让他参与锁的竞争中去。

    public class Test_ContainerWithWait {
        //及时可见
        volatile List list = new ArrayList<>();
    
        private void add(Object o) {
            list.add(o);
        }
    
        private Integer size() {
            return list.size();
        }
    
        public static void main(String[] args) {
            Test_ContainerWithWait container = new Test_ContainerWithWait();
            //用来上锁的对象
            Object o = new Object();
            new Thread(() -> {
                System.out.println("T2 开始");
                if (container.size() != 5) {
                    try {
                        //wait()方法必须在同步关键字修饰的方法中才能调用。
                        synchronized (o) {
                            o.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("T2 结束");
            }, "T2").start();
            //保证线程2先运行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                System.out.println("T1 开始");
                for (int i = 1; i <= 10; i++) {
                    container.add(new Object());
                    System.out.println("add + " + i);
                    if (container.size() == 5) {
                        synchronized (o) {
                            //notify不释放锁
                            o.notify();
                        }
                    }
    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("T1 结束");
            }, "T1").start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    上面代码,执行结果如下,执行结果正常,但是,这是有问题的
    在这里插入图片描述
    notify不释放锁,只是让对象去争这把锁,其中真正起作用的还是Thread.sleep(1000);这个,T2先去执行了。如果是以下写法:

    new Thread(() -> {
                synchronized (o) {
                    System.out.println("T1 开始");
                    for (int i = 1; i <= 10; i++) {
                        container.add(new Object());
                        System.out.println("add + " + i);
                        if (container.size() == 5) {
    
                            //notify不释放锁
                            o.notify();
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T1 结束");
                }
            }, "T1").start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    我们把锁加在代码逻辑的最外层,结果如下:
    在这里插入图片描述
    notify不释放锁,T2在5这个时候还是拿不到锁。怎么解决呢?正确写法如下;

    public static void main(String[] args) {
            Test_ContainerWithWait container = new Test_ContainerWithWait();
            //用来上锁的对象
            Object o = new Object();
            new Thread(() -> {
                synchronized (o) {
                    System.out.println("T2 开始");
                    if (container.size() != 5) {
                        try {
                            //wait()方法必须在同步关键字修饰的方法中才能调用。
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T2 结束");
                    //唤醒锁让T1继续执行
                    o.notify();
                }
            }, "T2").start();
            //保证线程2先运行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                synchronized (o) {
                    System.out.println("T1 开始");
                    for (int i = 1; i <= 10; i++) {
                        container.add(new Object());
                        System.out.println("add + " + i);
                        if (container.size() == 5) {
                            //notify不释放锁
                            o.notify();
                            try {
                                //通过wait释放锁
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T1 结束");
                }
            }, "T1").start();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    两个地方,一个是在T1线程里通过wait释放锁,一个是在T2线程里notify让T1继续执行。结果如下:
    在这里插入图片描述

    4、使用CountDownLatch

    CountDownLatch字面意思是倒数门栓,也就是倒数计数多少个线程执行完毕了。 其允许 int个线程阻塞在一个地方,直至所有线程的任务都执行完毕。
    代码如下:

     public static void main(String[] args) {
            Test_ContainerWithLatch container = new Test_ContainerWithLatch();
            CountDownLatch countDownLatch = new CountDownLatch(1);
    
            new Thread(() -> {
                    System.out.println("T2 开始");
                    if (container.size() != 5) {
                        try {
                            //上锁,等待
                            countDownLatch.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T2 结束");
            }, "T2").start();
    
            new Thread(() -> {
                    System.out.println("T1 开始");
                    for (int i = 1; i <= 10; i++) {
                        container.add(new Object());
                        System.out.println("add + " + i);
                        if (container.size() == 5) {
                            //开门,执行T2
                            countDownLatch.countDown();
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T1 结束");
    
            }, "T1").start();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    执行结果如下:
    在这里插入图片描述
    当然如果sleep方法没加,这个也是不能实现的。解决办法就是加两个门栓!代码如下:

     public static void main(String[] args) {
            Test_ContainerWithLatch container = new Test_ContainerWithLatch();
            CountDownLatch countDownLatch = new CountDownLatch(1);
            CountDownLatch countDownLatch2 = new CountDownLatch(1);
    
            new Thread(() -> {
                    System.out.println("T2 开始");
                    if (container.size() != 5) {
                        try {
                            //上锁,等待
                            countDownLatch.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T2 结束");
                //叫醒T1
                countDownLatch2.countDown();
            }, "T2").start();
    
            new Thread(() -> {
                    System.out.println("T1 开始");
                    for (int i = 1; i <= 10; i++) {
                        container.add(new Object());
                        System.out.println("add + " + i);
                        if (container.size() == 5) {
                            //开门,执行T2
                            countDownLatch.countDown();
                            try {
                                //让t1等t2执行完
                                countDownLatch2.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                      /*  try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }*/
                    }
                    System.out.println("T1 结束");
    
            }, "T1").start();
           }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    5、Semaphore 实现

    Semaphore 允许几个线程同时执行,灯亮执行,灯灭不执行。其主要作用是用来限流。这里就用来上锁放行哈哈哈哈哈。

     public static void main(String[] args) {
            Test_ContainerWithSeam container = new Test_ContainerWithSeam();
            Semaphore semaphore1 = new Semaphore(0);
            Semaphore semaphore2 = new Semaphore(0);
    
            new Thread(() -> {
                    System.out.println("T2 开始");
                    if (container.size() != 5) {
                        try {
                            //上锁,等待
                            semaphore1.acquire();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T2 结束");
                //叫醒T1
                semaphore2.release();
            }, "T2").start();
    
            new Thread(() -> {
                    System.out.println("T1 开始");
                    for (int i = 1; i <= 10; i++) {
                        container.add(new Object());
                        System.out.println("add + " + i);
                        if (container.size() == 5) {
                            //开门,执行T2
                            semaphore1.release();
                            try {
                                //让t1等t2执行完
                                semaphore2.acquire();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    System.out.println("T1 结束");
    
            }, "T1").start();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    6、LockSupport实现

    LockSupport主要作用就是阻塞和唤醒线程。
    unpark()可以先于park调用,所以不需要担心线程间的执行的先后顺序。
    代码如下:

        static Thread t1, t2;
        public static void main(String[] args) {
            Test_ContainerWithSeam container = new Test_ContainerWithSeam();
    
             t2 = new Thread(() -> {
                System.out.println("T2 开始");
                //可以不用判断
                if (container.size() != 5) {
                    //上锁,等待
                    LockSupport.park();
                }
                System.out.println("T2 结束");
                //叫醒T1
                LockSupport.unpark(t1);
            }, "T2");
            t2.start();
    
            Thread t1 = new Thread(() -> {
                System.out.println("T1 开始");
                for (int i = 1; i <= 10; i++) {
                    container.add(new Object());
                    System.out.println("add + " + i);
                    if (container.size() == 5) {
                        //开门,执行T2
                        LockSupport.unpark(t2);
                        //让t1等t2执行完
                        LockSupport.park();
                    }
                }
                System.out.println("T1 结束");
            }, "T1");
            t1.start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    ArcGIS for js 缓冲(vue项目)
    打包和部署Java应用程序:Maven和Shell脚本的实用方法
    HR拥抱人工智能,8大场景重塑无限可能
    QGIS编译(跨平台编译)之四十二:PostgreSQL安装(Windows、Linux、MacOS环境下安装)
    当语文课本上的古诗词遇上拓世AI,文生图绘就东方美学画卷
    重装系统后电脑耳机插前面没有声音输出怎么办?
    Rust中的结构体
    基于AD Event日志检测NTDS凭据转储攻击
    E签宝面试题
    电池UN38.3试验概要内容有哪些,UN38.3认证
  • 原文地址:https://blog.csdn.net/liwangcuihua/article/details/126248815