线程基本通讯机制--wait和notify

1、线程基本通讯机制

1 wait和notify的用法

wait和notify是Java最基本的线程间通讯机制,体现了线程的交互,用于信息的传递。例如在生产者消费者模式中,利用阻塞和唤醒使不一样线程之间配合实现业务逻辑。java

阻塞阶段--wait,调用对象的wait方法,线程进入WAITING状态,阻塞挂起,释放锁
wait阻塞后,直到下面状况之一发生时,线程才会被唤醒。ide

  • 其余线程调用该对象的notify/notifyAll方法。
  • 带有超时参数的wait方法,发生超时。若是参数是0,则永久等待。
  • 调用线程中断interrupt()。线程在waiting状态,会自动响应中断,抛出中断异常。

唤醒阶段 --notify/notifyAll测试

  • notify:随机唤醒一个线程
  • notifyAll:唤醒全部线程

2 wait和notify性质

  1. 使用前须要拥有锁(monitor锁)
  2. 属于Object类,底层是final native方法,属于JVM层代码。
  3. wait和notify是最基本的线程通讯机制
  4. 同时拥有多把锁,须要注意锁的释放顺序
synchronized(this) { 
    while(条件){
       wait();     
    }
}
  • 若是wait方法没有修饰,表示当前对象this
  • 即便没有调用唤醒方法,线程仍有可能从挂起状态变为可运行状态(虚假唤醒)。为防患于未然,常见是不断测试该线程被唤醒的条件是否被知足,不知足则继续等待。
synchronized (resourceA) {
    synchronized (resourceB) {
        try {
            resourceA.wait(); // 只释放resourceA锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

注意点:调用某个对象的notify,只能唤醒与该对象对应的线程。调用wait也只释放当前的那把锁。this

3 wait原理

EntrySet:入口集;WaitSet:等待集;The owner:线程拥有锁。’
锁的运行原理:开始线程在入口集和等待集竞争锁【1】,此时线程A获取到了锁【2】,入口集和等待集中的线程进入BLOCKED。此时A能够正常运行完释放锁【6】,也能够调用wait释放锁进入等待集【3】。等待集线程被唤醒【4】后,进入另外一个等待集,与入口集的线程一块儿竞争锁【5】。线程

2、常见问题

1 wait和notify实现生产者消费者模式

生产者消费者模式能够解耦生产者和消费者,使二者更好地配合。
设计

// 生产和消费100个产品
public class ProducerConsumerModelByWaitAndNotify {
    public static void main(String[] args) {
        // 建立仓库
        Storage storage = new Storage();
        // 建立生产者消费者线程
        Thread producer = new Thread(new ProducerTask(storage));
        Thread consumer = new Thread(new ConsumerTask(storage));
        producer.start();
        consumer.start();
    }
}

class ProducerTask implements Runnable {
    private Storage storage;

    public ProducerTask(Storage storage) {
        this.storage = storage;
    }

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

class ConsumerTask implements Runnable {
    private Storage storage;

    public ConsumerTask(Storage storage) {
        this.storage = storage;
    }

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

class Storage {
    private int maxSize;
    private Queue<Date> storage;

    public Storage() {
        this.maxSize = 10; // 队列最大是10
        this.storage = new LinkedList<>();
    }

    /**
     * wait和notify须要首先获取到锁,所以须要使用synchronized方法或者同步代码块
     */
    public synchronized void put() {
        // 仓库已满,没法生产更多产品,让出锁
        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() {
        // 仓库为空,没法获取到产品,线程让出锁
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.poll();
        System.out.println("消费者消费产品,此时仓库产品数:" + storage.size());
        // 通知生产者生产
        notify();
    }
}

2. 为何wait须要放在同步代码块中使用,而sleep不须要?

主要是为了让通讯更加可靠,防止死锁、永久等待的发生。code

wait放到synchronized代码中对线程有必定的保护做用。假设没有synchronized的保护,线程A在运行到wait语句以前,切换到线程B执行了notify语句,此时执行了wait语句释放锁后,没有线程唤醒,致使了永久等待。对象

sleep方法是针对单个线程的,与其余线程无关,无需放入到同步代码块中。blog

3 为何wait和notify方法定义在Object中,而不是Thread中?

wait和notify是锁级别操做,而锁是属于某个对象的,锁标识在对象的对象头中。若是将wait和notify定义在线程中,则会有很大的局限性。例如每一个线程均可能会休眠。若是某个线程持有多个锁,并且锁之间是相互配合的时,wait方法在Thread类中,就没有办法实现线程的配合。队列

调用线程对象.wait会发生什么?

我的理解: 调用线程对象的wait方法,也就是说以线程为锁。wait和notify的初衷就是用来线程间通讯,若是以线程为锁,不利于设计流程。

相关文章
相关标签/搜索