Java多线程之wait和notify详解以及实现阻塞队列

文章目录

  • 一、wait()和notify()
    • Condition
  • 二、阻塞队列
    • JDK中的BlockingQueue
    • 自己实现一个阻塞队列


提示:以下是本篇文章正文内容,Java系列学习将会持续更新

一、wait()和notify()

wait()
  一旦执行此方法,当前线程就进入阻塞状态,期间会释放同步监视器(锁)。
notify()
  一旦执行此方法,就会唤醒被wait的一个线程,如果多个线程被wait,就随机唤醒一个线程。
nofifyAll()
  一旦执行此方法,就会唤醒所有被wait的线程。

注意
  1.wait()和notify()方法是属于Object类的, Java中的对象都带有这两个方法。
  2.要使用wait和notify, 必须首先对“对象”进行synchronized加锁。


面试题说说sleep和wait的异同
  相同点:都会使一个线程进入阻塞状态。
  不同点:1.sleep()是定义在Thread类中的,而wait()是定义在Object类中的。
      2.sleep()不会释放同步锁,而wait()会释放同步锁。

import java.util.concurrent.TimeUnit;
/**
 * wait()和notify()用法
 *   1. 要使用wait和notify, 必须首先对“对象”进行synchronized加锁。
 *   2. wait()过程中是不持有锁的
 */
public class Demo1 {

    static class MyThread extends Thread {

        Object o;
        MyThread(Object o) {
            this.o = o;
        }

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o) {
                System.out.println("唤醒主线程");
                o.notify();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        Object o = new Object();

        synchronized (o) {
            // 确保主线程先抢到锁
            MyThread t = new MyThread(o);
            t.start();

            o.wait(); //1.释放o锁;2.等待... 3.再次加锁
            // wait()过程中是不持有锁的,否则代码中的子线程就无法抢到锁去唤醒主线程
        }

        System.out.println("终于被唤醒了!");
    }
}

回到目录…

Condition

Condition介绍:
  关键字synchronized可以与wait()和notify()方法相结合实现等待/通知模式。
  类ReentrantLock也可以实现同样的功能,但需要借助condition对象。

  condition类是在JDK5中出现的技术,使用他有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里可以创建多个condition实例,线程对象可以注册在指定的condition中从而选择性的进行线程通知,在调度线程上更加灵活。
  而在使用notify()/notifyAll()方法进行通知时,被调度的线程却是由JVM随机选择的。但使用ReentrantLock结合condition类是可以实现“选择性通知”,这个功能是非常重要的,而且在condition类中默认提供的。

  ① Condition是个接口,基本的方法就是await()和signal()方法
  ② Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition();
  ③ 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用.
  ④Conditon中的await()对应Object的wait();
   Condition中的signal()对应Object的notify(); .
   Condition中的signalAll()对应Object的notifyAll()。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Condition的用法
 */
public class Demo {
    public static void main(String[] args) throws InterruptedException {

        Lock lock = new ReentrantLock();

        Condition condition = lock.newCondition();

        condition.await();   // 等价于 wait
        condition.signal();  // 等价于notify
        condition.signalAll();

        lock.lock();   // 也需要获取到锁才能用

        try {
            condition.await();
        } finally {
            lock.unlock();
        }
    }
}

回到目录…

二、阻塞队列

  阻塞队列(BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。

  阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

JDK中的BlockingQueue

java.util.concurrent.BlockingQueue接口
ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue 一个由链表结构组成的无界阻塞队列
PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列
DelayQueue 一个使用优先级队列实现的无界阻塞队列
SynchronousQueue 一个不存储元素的阻塞队列
LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列

对于 BlockingQueue 的阻塞队列提供了四种处理方法

方法描述 抛出异常 返回特殊的值 一直阻塞 超时退出
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
返回头 element() peek() 不可用 不可用

自己实现一个阻塞队列

以生产者-消费者为例实现一个阻塞队列:
 ①加锁保证线程安全。
 ②wait和notifyAll实现等待和唤醒。
 ③使用while而不是if的原因。
 ④使用notifyAll的原因。

/**
 * 实现一个阻塞队列
 */
public class MyArrayBlockingQueue {

    private long[] array;
    private int frontIndex; // 永远在队列的第一个元素位置
    private int rearIndex;  // 永远在队列的最后一个的下一个位置
    private int size; // 元素的个数

    public MyArrayBlockingQueue(int capacity) {
        array = new long[capacity];
        // 定义初始状态
        frontIndex = 0;
        rearIndex = 0;
        size = 0;
    }

    // 加锁线程安全
    public synchronized void put(long e) throws InterruptedException {
        // 先判断是否满
        while(size == array.length){
            // TODO: 满
            this.wait();
            /**
             * 为什么使用while而不是if?
             *   因为在多对多的情况下,该线程被唤醒后不一定能抢到锁。
             *   当自己抢到锁后就不一定满足"队列未满"的条件,有可能已经被之前抢到锁的线程填满了。
             *   如果是if,就会在抢到锁后直接执行后面的代码,不会再次判断。所以需要while语句多次判断。
             */
        }

        // 还没有满
        array[rearIndex] = e;
        // 因为是循环队列,所以不能简单的++操作
        rearIndex = (rearIndex + 1) % array.length;
        size ++;
        this.notifyAll();
        /**
         * 为什么使用notifyAll,而不是notify?
         *   如果只有一个消费者和一个生产者的话,消费者唤醒的一定是生产者,生产者唤醒的一定是消费者。
         *   但如果是多对多的场景下,就有可能出现消费者唤醒消费者的情况(因为notify是随机的)。
         *   最坏的情况下,多个消费者唤醒的都是消费者,而所有生产者都在wait中,出现死锁的情况。
         */
    }

    public synchronized long take() throws InterruptedException {
        // 先判断是否为空
        while(size == 0){
            // TODO: 空
            this.wait();
        }

        // 不空
        long ret = array[frontIndex];
        frontIndex = (frontIndex + 1) % array.length;
        size --;
        this.notifyAll();
        return ret;
    }
}

回到目录…


总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是Java多线程的学习,详细介绍了wait()和notify()的用法,以及什么是阻塞队列,如何实现它?之后的学习内容将持续更新!!!

你可能感兴趣的:(Java多线程与并发,java,面试,开发语言)