Java高并发编程之synchronized与wait和notify结合

有一道面试题是这样的:实现一个容器,提供两个方法,add和size。写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5时,线程2给出提示并结束。
咋一看,似乎并不能,首先,实现一个容器如下:

public class MyContainer1 {
    List<Object> lists = new ArrayList<>();
    public void add(Object o) {
        lists.add(o);
    }
    public int size() {
        return lists.size();
    }
}

然后,在main方法中写两个线程,第一个线程,往容易中添加10个元素,第二个线程,使用while循环,当发现容器的元素个数为5时,跳出while循环, 并结束线程,代码如下:

public static void main(String[] args) {
        MyContainer1 t1 = new MyContainer1();
        new Thread(()->{
            for(int i = 0; i < 10; i++) {
                try {
                    t1.add("");
                    System.out.println(t1.size());
                    Thread.sleep(1000);
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }, "t1").start();

        new Thread(()->{
            while (true) {
                if(t1.size() == 5) {
                    System.out.println("list size is 5, break");
                    break;
                }
            }
        }, "t2").start();
    }

看起来是没问题,然而事情并不会这么简单,问题出在哪呢?忘记加volatile了!给lists加上volatile修饰:volatile List<Object> lists = new ArrayList<>();再运行程序,便能得到想要的效果。
但是这个程序就是完善的吗?当然不,其问题在于,线程2中的while循环,非常浪费CPU,另外就是,由于没有加同步,即使是while循环,也不能非常精准地在判断size为5的时候break,有可能在执行判断的时候,size又改了。所以,我们还需要做到更好!
这里使用wait和notify可以做到更好,wait会释放锁,而notify不会释放锁。
首先使用synchronized某个锁定对象,然后调用该对象的wait方法,线程进入等待状态,同时释放锁;只有在该对象的notify方法被调用的时候,等待状态的线程才会被唤醒继续执行。
事不宜迟,写下代码:

public class MyContainer2 {
    private List<Integer> lists = new ArrayList<>();
    public void add(Integer a) {
        lists.add(a);
    }
    public Integer size() {
        return lists.size();
    }

    public static void main(String[] args) {
        MyContainer2 c = new MyContainer2();
        final Object lock = new Object();
        
        //线程2:监控线程
        new Thread(()->{
            synchronized (lock) {
                System.out.println("thread 2 start...");
                if(c.size() != 5) {
                    try {
                        lock.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("thread 2 end.");
            }
        }, "t2").start();
        
        new Thread(()->{
            synchronized (lock) {
                for(int i = 0; i < 10; i++) {
                    System.out.println("thread 1, add " + i);
                    c.add(i);
                    if(c.size() == 5) {
                        lock.notify();
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "t1").start();
    }
}

在上面的代码中,线程2先启动,它首先获得lock对象的锁,判断容器size不为5时,调用lock对象的wait方法开始等待,同时释放lock对象的锁;接着线程1启动,同样首先获取lock对象的锁,然后for循环每次像容器中添加一个元素,当判断容器size为5的时候,调用lock的notify方法,理论上这个时候会唤醒处于wait状态t2线程,然后t2应该输出"thread 2 end."
运行代码,发现size为5的时候,t2并没有继续执行!问题在于,wait会释放锁,而notify不会释放锁,尽管notify方法被调用了,但是t2并没有获得锁,因此不会继续执行。t2要想重新获得锁,当然要t1释放才行,所以,在t1中,调用了lock对象的notify方法之后,再调用lock的wait方法释放锁,而在t2被唤醒之后,继续执行,最后还要调用lock对象的notify方法去唤醒此时处在wait状态的t1,于是代码优化如下:

public class MyContainer2 {

    private List<Integer> lists = new ArrayList<>();
    public void add(Integer a) {
        lists.add(a);
    }
    public Integer size() {
        return lists.size();
    }

    public static void main(String[] args) {
        MyContainer2 c = new MyContainer2();
        final Object lock = new Object();

        //监控线程
        new Thread(()->{
            synchronized (lock) {
                System.out.println("thread 2 start...");
                if(c.size() != 5) {
                    try {
                        lock.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("thread 2 end.");
                lock.notify();
            }
        }, "t2").start();
        
        new Thread(()->{
            synchronized (lock) {
                for(int i = 0; i < 10; i++) {
                    System.out.println("thread 1, add " + i);
                    c.add(i);

                    if(c.size() == 5) {
                        lock.notify();
                        try {
                            lock.wait();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "t1").start();
    }
}

使用synchronized、wait和notify,终于实现了想要的效果,相比第一种实现方式,已经有了极大的进步,至少没有硬伤了,但这依然不是最好的解决方案,下一篇文章继续讲解。

猜你喜欢

转载自blog.csdn.net/weixin_42486373/article/details/83963088