一、wait()和notify()作用
wait()和notify()方法是Object类的方法,因为Object类是所有类的根类,因此所有类都有这两个方法。
当调用对象的wait()方法时会释放获取的该对象的锁。
既然是释放锁,我们首先要有锁才能释放,因此wait()方法必须在同步方法或同步代码块中才能执行即在synchronized修饰的方法或代码块。
当调用对象的notify()方法时会唤醒等待获取该对象锁的一个线程,还有一个方法notifyAll()是唤醒等待获取该对象锁的所有线程。
同理要唤醒其他等待获取对象锁的线程,必须先拥有该对象锁,因为如果锁都不在自己手里,唤醒其他等待该对象锁的线程又有什么意义,自己又没有能力把锁释放掉交给别人。因此,notify()方法也需要在synchronized()代码块中执行。
二、synchronized关键字作用
既然wait()和notify()方法都涉及到synchronized关键字,我们就来看看这个关键字的作用。
synchronized关键字的作用是在被修饰的方法或代码块的开始和结束分别加上monitorenter和monitorexit字节码指令,当执行monitorenter指令时对象头中的一个计数器会加一,执行monitorenter指令时,对象头中的计数器会减一。需要注意的是该锁是可重入锁,即如果已经获取到了对象锁,可以重复获取,但monitorenter和monitorexit必须保持一一对应。
synchronized关键字用于修饰方法或者代码块,修饰方法时整个方法都被同步,同一时间只能有一个线程可以访问,修饰代码块时同理同一时间只能一个线程可以访问,但是代码块所在的方法里的其他部分仍然是同时可以被不同线程访问。
synchronized可以用于修饰普通方法和静态方法,修饰普通方法时获取的就是对象的锁,修饰静态方法时获取的就是实例化对象的类的Class对象的锁。
三、生产者消费者模式
生产者消费者模式顾名思义就是生产者不断生产产品,消费者不断消费产品,通常生产的产品会有一定的数量限制,如果生产的产品数量达到上限,必须停止生产,直到产品数量小于最大限制时才可以继续生产。如果没有任何产品可以消费,消费者必须等待直到有产品才可以继续消费。通过代码表达就是有两个线程,一个固定大小的队列。一个线程负责向队列中插入数据,另一个线程从线程中获取数据。为了达到上面的生产、消费条件需要通过wait()方法和notify()方法来实现。并且wait()和notify()方法的调用时机不同,可能会产生不同的结果,比如随机生产消费和生产一件产品消费一件产品等多种结果,下列代码是顺序生产和消费方式的实现。
public class TestProducterConsumer {
private int queueSize = 10;
// 固定大小的队列
private PriorityQueue<Integer> queue = new PriorityQueue<>(queueSize);
public static void main(String[] args) throws InterruptedException {
TestProducterConsumer testProducterConsumer = new TestProducterConsumer();
Producter producter = testProducterConsumer.new Producter();
Consumer consumer = testProducterConsumer.new Consumer();
producter.start();
Thread.sleep(1000);// 顺序执行生产消费
consumer.start();
}
//生产者线程
class Producter extends Thread {
@Override
public void run() {
while (true) {
//同步代码块,获取队列锁
synchronized (queue) {
//当队列不满时生产者可以继续生产,生产之后唤醒消费者
//唤醒消费者,在生产者释放锁之后,消费者不一定就会获取锁,也许是生产者获取到锁继续执行
//但是如果不唤醒消费者,当队列满时,如果消费者处于阻塞状态,那么生产者和消费者都处于阻塞状态,程序就无法继续执行
if (queue.size() < queueSize) {
queue.add(queue.size() + 1);
System.out.println("生产者向队列中加入产品P,队列剩余空间:" + (queueSize - queue.size()));
//模拟生产者生产过程,sleep不会释放锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒消费者
//1)随机生产和消费
queue.notify();
//2)顺序生产和消费
try {
//生产之后进入立刻阻塞状态,让消费者消费
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
} else {
//队列已满,进入阻塞状态,等待消费者消费
System.out.println("队列已满等待消费者消费");
try {
queue.wait();//1)随机生产和消费
} catch (InterruptedException e) {
queue.notify();
}
}
}
}
}
}
//消费者线程
class Consumer extends Thread {
@Override
public void run() {
while (true) {
//同步代码块,获取队列锁
synchronized (queue) {
//如果队列是空的,消费者进入阻塞状态,等待生产者生产并唤醒
if (queue.isEmpty()) {
System.out.println("没有产品可以消费,进入阻塞状态等待生产者生产。");
//进入阻塞状态释放队列锁,因为只有两个线程,所以生产者一定会获取到队列锁执行
try {
queue.wait();
} catch (InterruptedException e) {
queue.notify();
}
} else {
//如果队列不空,就消费产品,并唤醒生产者
//注意唤醒生产者,在消费者执行完毕释放锁之后,不一定生产者就会获得锁,也许消费者会继续获取锁执行
//但是如果不唤醒生产者,那么如果生产者处于阻塞状态,当队列为空,消费者也进入阻塞状态那么就没有线程可以获取锁继续执行了
queue.notify();//1)随机生产和消费
//模拟消费者消费过程,sleep不会释放锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
queue.poll();
System.out.println("消费者消费了产品P,剩余空间:" + (queueSize - queue.size()));
//2)顺序生产和消费
try {
//消费之后立刻进入阻塞状态,让生产者生产
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
}
}
}
}
}