java村支书 · 2019年12月11日

一道关于线程相关的面试题

面试的时候,面中多线程,线程安全的概率蛮大的。笔者之间就遇到这到面试题,今突然想到这该死的面试题,还好忙里偷闲,拿来重新思考。

面试题

实现一个容器,提供两个方法,分别为:1、add()用于添加元素。2、size()用于获取容器长度。使用多线程实现两个要求:

1,线程1添加10个元素到容器中,

2,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束

以下给出3种方案

第一种方案

考察知识点:volatile 关键字,请看代码:

public class Demo01 {

    List<Object> list = new ArrayList<>();

    /**
     * 1、add()用于添加元素。
     * @param o
     */
    public void addObject(Object o){
        list.add(o);
    }

    /**
     * 2、size()用于获取容器长度
     * @return
     */
    public int getSize(){
        return list.size();
    }


    public static void main(String[] args) {
        Demo01 demo01 = new Demo01();

        //线程t2用于监控容器长度
        new Thread(()->{
            System.out.println("t2线程启动,开始监控...");
            while(true){
                if(demo01.getSize() == 5){
                    break;
                }
            }
            System.out.println("当前容器长度为:" + demo01.getSize()+",t2线程结束...");
        },"t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            System.out.println("t1线程启动,开始添加元素...");
            for(int i=0;i<10;i++){
                demo01.addObject(new Object());
                System.out.println("当前容器长度为:" + demo01.getSize());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();

    }
}

最后执行,结果:

file

什么情况??没监控到,并且线程t2一直在运行。。。原因:这里list在两个线程之间不保证可见性,所以线程t2始终结束不了(也许会结束,但是,不知道什么时候能结束)。

因此要将 List<Object> list = new ArrayList<>() 改为 volatile List<Object> list = new ArrayList<>() 即可。

file

再一次输出:

file

此种方案,利用了volatile可见性的特点。但是这种方式浪费cpu资源。

第二种方案

加锁机制,见如下代码:

  public class Demo2 {

    volatile List<Object> list = new ArrayList<>();

    /**
     * 1、add()用于添加元素。
     * @param o
     */
    public void addObject(Object o){
        list.add(o);
    }

    /**
     * 2、size()用于获取容器长度
     * @return
     */
    public int getSize(){
        return list.size();
    }


    public static void main(String[] args) {
        Demo2 demo2 = new Demo2();
        Object lock = new Object();

        new Thread(()->{
            synchronized (lock){
                System.out.println("t2线程启动,开始监控...");
                if(demo2.getSize() != 5){
                    try {
                        lock.wait();//释放锁,让t1运行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();//唤醒t1
                System.out.println("当前容器长度为:" + demo2.getSize()+",t2线程结束...");
            }

        },"t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            synchronized (lock){
                for(int i=0;i<10;i++){
                    demo2.addObject(new Object());
                    System.out.println("当前容器长度为:" + demo2.getSize());
                    if(demo2.getSize() == 5){
                        lock.notify();//唤醒t2
                        try {
                            lock.wait();//释放锁,让t2执行
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }

        },"t1").start();

    }
}

输出结果:

file

这种方案用synchronized加wait,notify就显得太重了

第三种方案

CountDownLatch发令枪机制,见如下代码:

public class Demo3 {

    volatile List<Object> list = new ArrayList<>();

    /**
     * 1、add()用于添加元素。
     * @param o
     */
    public void addObject(Object o){
        list.add(o);
    }

    /**
     * 2、size()用于获取容器长度
     * @return
     */
    public int getSize(){
        return list.size();
    }

    public static void main(String[] args) {
        Demo3 demo3 = new Demo3();
        CountDownLatch countDownLatch = new CountDownLatch(5);

        new Thread(()->{
            System.out.println("t2线程启动,开始监控...");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前容器长度为:" + demo3.getSize()+",t2线程结束...");
        },"t2").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            for(int i=0;i<10;i++){
                demo3.addObject(new Object());
                countDownLatch.countDown();
                System.out.println("当前容器长度为:" + demo3.getSize());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();

    }

}

这种使用 CountDownLatch方式,相当于是发令枪,运动员线程调用await等待,计数到0开始运行。其实就是 使用await和countdown方法替代wait和notify。 CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行

以上就是三个方案,当然如果很熟练多线程并发编程的话,还有很多种方案。

本人水平有限,难免有错误或遗漏之处,望大家指正和谅解,提出宝贵意见,愿与之交流。

推荐阅读
关注数
0
文章数
16
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息