Java的Object类提供了wait和notify方法用于实现线程间通信(因为所有的java类都继承了Object类,所以所有的java类都有这两个方法)。这两个方法在Object类中签名如下:
public final native void wait(long timeout) throws InterruptedException;
/**
*唤醒一个等待此对象(notify方法所属的对象)监视器(monitor)的线程。如果有多个*线程等待此对象的监视器,它们中的一个会被选中唤醒。至于选择哪个线程,是任意的,由*具体的底层实现决定。一个线程通过调用此对象的wait方法来等待此对象的监视器。
*被唤醒的线程不是立刻就能接着执行的,而是要等待当前占有此对象监视器的线程(一般就*是执行notify的线程走出同步方法后,释放锁)释放此对象的锁。
*/
public final native void notify();
需要说明的是,wait和notify方法都必须
配合synchronized关键字使用。只有进入了synchronized方法/代码块,拿到了对象锁之后,才能调用该锁对象的wait或notify方法。执行锁对象的wait方法后会释放掉锁,并阻塞住。这样别的线程才有机会获取对象锁,从而才有机会执行synchronized方法/代码块中的锁对象的notify方法。
如果没有拿到锁对象的锁,就调用锁对象的wait或notify方法,会报错java.lang.IllegalMonitorStateException
。
下面通过用LinkedList和wait、notify模拟一个队列实现:
package com.pilaf.test;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description:利用wait和notify方法模拟实现一个阻塞队列
* @author: pilaf
* @create: 2018-10-28 20:52
*/
public class MyQueue<T> {
/**
* 内部盛放元素的容器
*/
private final LinkedList<T> list = new LinkedList<T>();
/**
* 锁对象
*/
private Object lock = new Object();
/**
* 队列元素个数计数器
*/
private volatile AtomicInteger count = new AtomicInteger();
/**
* 队列最大容量
*/
private final int size ;
public MyQueue(int initialSize) {
this.size = initialSize;
}
public MyQueue() {
this(Integer.MAX_VALUE);
}
/**
* 往队列中放入元素
* @param element
* @throws InterruptedException
*/
public void put(T element) throws InterruptedException{
//获取锁
synchronized (lock) {
//如果队列满了,释放掉锁,阻塞住
if (count.get() == size) {
lock.wait();
}
//能够走到这一步说明队列还有空间,把元素放入队列中
list.add(element);
//增加队列元素计数器值
count.incrementAndGet();
//当容器空的时候,其他线程调用队列pop方法的时候会阻塞在wait方法后,当put进新元素的时候去唤醒被阻塞的线程
lock.notify();
}
}
/**
* 从队列取一个元素
* @return
* @throws InterruptedException
*/
public T pop() throws InterruptedException{
T result = null;
synchronized (lock) {
//容器为空
if(count.get()==0){
//释放lock,阻塞住
lock.wait();
}
//能走到这一步说明内部容器list不为空,返回第一个元素
result = list.removeFirst();
//容器元素个数计数器减一
count.decrementAndGet();
//当容器满的时候,别的线程阻塞在put方法,这儿取走一个元素后唤醒其他阻塞在put的线程
lock.notify();
return result;
}
}
}
package com.pilaf.test;
/**
* @description:
* @author: pilaf
* @create: 2018-10-28 21:55
*/
public class MyQueueTest {
public static void main(String[] args) {
//初始化一个容量为4的队列
MyQueue<Integer> myQueue = new MyQueue<Integer>(4);
new Thread(new PutThread(myQueue)).start();
new Thread(new PopThread(myQueue)).start();
}
static class PutThread implements Runnable {
private MyQueue<Integer> myQueue;
public PutThread(MyQueue<Integer> myQueue) {
this.myQueue = myQueue;
}
public void run() {
for(int i = 0;i<100;i++) {
try {
myQueue.put(i);
System.out.println("put " + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class PopThread implements Runnable {
private MyQueue<Integer> myQueue;
public PopThread(MyQueue<Integer> myQueue) {
this.myQueue = myQueue;
}
public void run() {
while (true) {
try {
Integer i = myQueue.pop();
System.out.println("pop " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
读者可以调整PutThread类中run方法中的睡眠时间和PopThread类中run方法中的睡眠时间,让两个线程的睡眠时间有长有短可以看到一个线程快速往队列里放元素或取元素的时候会阻塞住,而一旦慢的线程取走或者放入元素,快的线程又能立马放入或取走元素。