wait()、 notify()、 notifyAll()相关的问题

wait()、 notify()、 notifyAll()特点与性质

  1. 使用必须先拥有monitor
  2. notify只能唤醒任何一个
  3. 都属于object类
  4. 释放锁只会释放当前锁

使用wait和notify实现生产者与消费者

/**
 * 〈用wait/notify实现生产者和消费者〉
 *
 * @author Chkl
 * @create 2020/2/29
 * @since 1.0.0
 */
public class ProducerCustomerModel {

    public static void main(String[] args) {

        EventStorage storage = new EventStorage();
        Producer producer = new Producer(storage);
        Consumer consumer = new Consumer(storage);

        new Thread(producer).start();
        new Thread(consumer).start();


    }

}

//生产者
class Producer implements Runnable {
    private EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}

//消费者
class Consumer implements Runnable {
    private EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}

//阻塞队列
class EventStorage {
    //    最大值
    private int maxSize;
    //    数据存储队列
    private LinkedList<Date> storage;

    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<>();
    }

    //    添加方法
    public synchronized void put() {
//        当队列满了就调用wait方法释放锁,等待消费后唤醒
        while (storage.size() == maxSize) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        添加到队列
        storage.add(new Date());
        System.out.println("仓库有了" + storage.size() + "个产品");
//        添加完成后提醒消费者消费
        notify();
    }

    public synchronized void take() {
//        当队列空了调用wait方法释放锁等待生成
        if (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        进行消费
        System.out.println("拿到了" + storage.poll() + ",现在还剩下" + storage.size());
//        消费后提醒生产者进行生产
        notify();
    }

}

用程序实现两个线程交替打印0-100的奇偶数

synchronized实现
/**
 * 〈用程序实现两个线程交替打印0-100的奇偶数
 * 本类采用synchronized〉
 *
 * @author Chkl
 * @create 2020/2/29
 * @since 1.0.0
 */
public class waitnotifyPrintEvenSYyn {

    private static int count;
    private static Object lock = new Object();

    //建两个线程,一个只处理偶数,一个只处理奇数(位运算)
    //用synchronized做通信
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count < 100) {
                    synchronized (lock) {
                        if ((count & 1) == 0) {
                            System.out.println
                                    (Thread.currentThread().getName()
                                            + ":" + count++);
                        }
                    }
                }
            }
        }, "偶数").start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count < 100) {
                    synchronized (lock) {
                        if ((count & 1) != 0) {
                            System.out.println
                                    (Thread.currentThread().getName()
                                            + ":" + count++);
                        }
                    }
                }
            }
        }, "奇数").start();
        
    }
}
wait和notify优化实现
/**
 * 〈连个线程交替打印0-100的两个奇偶数〉
 * 用wait和notify实现
 *
 * @author Chkl
 * @create 2020/2/29
 * @since 1.0.0
 */
public class WaitNotifyprintEvenWait {
    //拿到锁,就打印
    //打印完,唤醒其他线程,就休眠

    private static int count;
    private static Object lock = new Object();

    static class TurningRunning implements Runnable {
        @Override
        public void run() {
            while (count <= 100) {
                synchronized (lock) {
                    System.out.println
                            (Thread.currentThread().getName() + ":" + count++);
                    lock.notify();
                    if (count <= 100) {
                        try {
                            //如果任务未结束,让出当前的锁
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new TurningRunning(), "偶数").start();
        try {
            //休眠100毫秒,保证偶数先行
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new TurningRunning(), "奇数").start();


    }

}

为什么wait()需要在同步代码块内使用,而sleep()不用

  • 为了让通信变得可靠,防止死锁或者永久等待的发生

如果不把wait和notify方法都放在同步块里,可能在执行wait之前,线程突然切出去了,到一个将要执行notify的线程,把notify的都执行完了之后再切回将执行wait的线程执行完wait之后,不再有线程唤醒它,造成永久等待

  • sleep主要针对单个线程的,和其他线程关系都不大,所以不需要在同步代码块中执行

为什么线程通信的方法wait(),notify(),notifyAll()被定义在Object类里?而sleep()定义在Thread类里?

  • wait(),notify(),notifyAll()是锁级别的操作,而锁是属于某一个对象的。每一个对象的对象头中都有几个字节是存放锁的状态的,所以锁是绑定在对象中,而不是线程中。如果定义在Thread中,如果每一个线程持有多把锁,就不能灵活地使用了。

  • sleep()是针对于单个线程的操作,所以在Thread类中

wait方法属于Object类,那么调用Thread.wait()会怎样呢?

  • 可以当成一把锁去进行wait和notify的操作
  • 当线程退出的时候会自动的执行notify,会干扰设计的整个流程

如何选择notify()和notifyAll()

根据业务需求选择

  • notify()随机的唤醒一个线程
  • notifyAll()唤醒所有的wait状态的线程

notifyAll之后所有的线程会再次抢夺锁,如果线程抢夺失败怎么办?

陷入等待状态,等待这把锁被释放后再次竞争

用suspend和resume来阻塞线程可以吗?

由于安全问题,这两个方法都已弃用了。