JAVA - 多线程 - Object.wait / notify / notifyAll & LockSupport.park / unpark

先来看一道淘宝面试题

实现一个容器, 提供两个方法add() , size(), 需要写两个线程

  • 线程1: 添加10个元素到容器中, 得全部执行完
  • 线程2: 实现监控元素的个数, 当个数到5个时, 打印"监控结束" , 线程2结束

普通人的错误解法 ( 主要用于深入表明题意 )

public class T01_WithoutVolatile {
    private List lists = new LinkedList();
    public void add(Object o) {
        lists.add(o);
    }
    public int size() {
        return lists.size();
    }

    public static void main(String[] args) {
        T01_WithoutVolatile c = new T01_WithoutVolatile();
        Thread addThread = new Thread() {
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    c.add(new Object());
                    System.out.println("已添加第" + i + "个");
                    c.sleep(300); // 只有sleep了, 才能保证在第5个之后, 第6个之前, 线程2打印结束
                }
            }
        };
        Thread sizeThread = new Thread() {
            public void run() {
                while (c.size() != 5) {
                    c.sleep(10); // 只有sleep了, 才能保证在第5个之后, 第6个之前, 线程2打印结束
                }
                System.out.println("size==5, 监控结束");
            }
        };
        addThread.start();
        sizeThread.start();
    }

    private static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (Exception e) {
        }
    }
}

运行结果有可能正确, 但也有可能是下面的结果, 第6个打印完, 才打印结束

已添加第1个
已添加第2个
已添加第3个
已添加第4个
已添加第5个
已添加第6个
size==5, 监控结束
已添加第7个
已添加第8个
已添加第9个
已添加第10个

这个解法主要是讲明题意, sleep严重影响性能,肯定不能作为答案

wait / notify 的错误解法


public class T03_NotifyHoldingLock {
    private List lists = new LinkedList();
    public void add(Object o) {
        lists.add(o);
    }
    public int size() {
        return lists.size();
    }

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

        Thread addThread = new Thread() {
            public void run() {
                synchronized (lock) { // 获取lock锁
                    for (int i = 1; i <= 10; i++) {
                        c.add(new Object());
                        System.out.println("已添加第" + i + "个");
                        if (i == 5) {
                            lock.notify(); // 只做到通知, 其他线程想要开始做, 得先等我全部做完
                        }
                    }
                    // 释放lock锁
                }
            }
        };
        Thread sizeThread = new Thread() {
            public void run() {
                synchronized (lock) { // 获取lock锁
                    try {
                        lock.wait(); // 释放lock锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("size==5, 监控结束");
                }
            }
        };
        // 必须保证sizeThread先跑起来, 否则程序会因为sizeThread没有被notify, 永远也退出不了
        sizeThread.start();
        addThread.start();
    }
}
Object.wait / notify / notifyAll, 很重要的容易被忽略的点
  • wait()调用完了会释放当时synchronized获得的锁
  • notify调用完了, 不会释放锁, 其他的线程虽然被notify了, 但是无法立即拿到锁

示例中, 我们先让sizeThread跑起来, 获得lock锁之后, 调了wait方法又立即释放了lock
接下来, addThread跑起来了, 当执行到第5次循环的时候, 调用了notify, 但是这个时候并没有释放锁,
只是让sizeThread从对象池的阻塞队列中, 转移到了等待队列, 而等待队列中的线程想要继续执行, 得先获取到锁才行
但是addThread没有释放锁, 执行完所有10次循环, 才退出了synchronized同步代码块, 才释放了锁

wait / notify 的正确解法

public class T04_NotifyFreeLock {
    private List lists = new LinkedList();
    public void add(Object o) {
        lists.add(o);
    }
    public static void main(String[] args) {
        T04_NotifyFreeLock c = new T04_NotifyFreeLock();
        Object lock = new Object();

        Thread addThread = new Thread() {
            public void run() {
                synchronized (lock) {
                    for (int i = 1; i <= 10; i++) {
                        c.add(new Object());
                        System.out.println("已添加第" + i + "个");
                        if (i == 5) {
                            lock.notify(); // 通知sizeThread到等待队列
                            try {
                                lock.wait(); // 自己立即阻塞, 并释放锁, 让sizeThread先执行
                            } catch (InterruptedException e) {
                            }
                        }
                    }
                }
            }
        };
        Thread sizeThread = new Thread() {
            public void run() {
                synchronized (lock) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                    }
                    System.out.println("size==5, 监控结束");
                    lock.notify(); // 通知addThread恢复
                }
            }
        };
        // 必须保证sizeThread先跑起来, 否则程序会因为sizeThread没有被notify, 永远也退出不了
        sizeThread.start();
        addThread.start();
    }
}

这里的关键点是, addThread notify了之后, 自己立即阻塞, 并释放锁, 给sizeThread一次执行的机会
sizeThread在打印结束之后, 要记得再notify一下addThread, 让addThread继续把后面5次循环执行完, 否则addThread会一直等在那里

差不多的解决思路, CountDownLatch的解法

public class T05_CountDownLatch {
    private List lists = new LinkedList();

    public void add(Object o) {
        lists.add(o);
    }

    public static void main(String[] args) {
        T05_CountDownLatch c = new T05_CountDownLatch();
        CountDownLatch cdl = new CountDownLatch(1);
        CountDownLatch cdl2 = new CountDownLatch(1);

        Thread addThread = new Thread() {
            public void run() {
                try {
                    for (int i = 1; i <= 10; i++) {
                        c.add(new Object());
                        System.out.println("已添加第" + i + "个");
                        if (i == 5) {
                            cdl.countDown();
                            cdl2.await();
                        }
                    }
                } catch (Exception e) {
                }
            }
        };
        Thread sizeThread = new Thread() {
            public void run() {
                try {
                    cdl.await();
                    System.out.println("size==5, 监控结束");
                    cdl2.countDown();
                } catch (Exception e) {
                }
            }
        };
        addThread.start();
        sizeThread.start();
    }
}

一样的思路, addThread通知sizeThread之后, 自己先趴着(阻塞), 等待sizeThread执行完后notify自己

类似的思路, LockSupport的解法

public class T06_LockSupport {
    private List lists = new LinkedList();

    public void add(Object o) {
        lists.add(o);
    }

    static Thread addThread = null, sizeThread = null;

    public static void main(String[] args) {
        T06_LockSupport c = new T06_LockSupport();

        addThread = new Thread() {
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    c.add(new Object());
                    System.out.println("已添加第" + i + "个");
                    if (i == 5) {
                        LockSupport.unpark(sizeThread);
                        LockSupport.park();
                    }
                }
            }
        };
        sizeThread = new Thread() {
            public void run() {
                LockSupport.park();
                System.out.println("size==5, 监控结束");
                LockSupport.unpark(addThread);
            }
        };
        // 必须保证sizeThread先跑起来, 否则程序会因为sizeThread没有被notify, 永远也退出不了
        sizeThread.start();
        addThread.start();
    }
}

.

LockSupport.park / unpark

LockSupport.park() 使当前线程阻塞
LockSupport.unpark(thread) 叫醒某个线程

演示: 在第5秒阻塞, 在第8秒叫醒

在这里插入图片描述

在park()被调用之前 , unpark可以先调用

在上面的示例中, sleepSeconds(8) 和 unpark(t) 两行互换位置, 运行结果显示unpark先调用的话, park()方法会不起作用

更进一步, 我们来测验一下分别调用两次park和unpark
在这里插入图片描述
其效果和分别调用一次一模一样

所以,可以证明LockSupport内部是根据count来判断是否要继续执行线程的, count初始为0, park一下, count++, unpark一下count–, 当count>=0的时候, 线程可以继续往下执行, 否则, 就阻塞住

本质上park和unpark都是native方法

    public native void park(boolean var1, long var2);
    public native void unpark(Object var1);

区别

如果用其他的锁方式, 你得有一把锁, LockSupport是不需要的

  • 比如wait, 你得有个object
  • synchronized你也得有个object, 比如XXX.class或new XXX()
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值