文章目录
- 一、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();
}
}
}
回到目录…
二、阻塞队列
在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
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()的用法,以及什么是阻塞队列,如何实现它?之后的学习内容将持续更新!!!