一个生产线程一个消费线程
notify是唤醒一个等待线程,如果有多个,会随机唤醒一个,其余仍然处在等待状态。
notifyAll唤醒所有的等待线程,如果有多个,会随机一个能重新获取锁,其他的会进入等待锁的状态。
缓冲区类BufferLock 中,value是共享变量,isEmpty 是否为空的信号量,并将put()和get()声明为同步方法。
每次只能生产一个产品,消费完后再生产下一个,依次交替,这个例子中使用notify,读者可以替换为notifyAll,不影响执行结果:
public class BufferLock {
private int value;
/**
* value是否为空的信令号量,可以理解存储产品的池子,容量只有1<br>
* 为空表示池子空了,可以放入数据,不可以取数据;<br>
* 非空表示池子满了,不可以放入数据,可以取数据
*/
private boolean isEmpty = true;
public synchronized void put(int i) {
if (!isEmpty) {
try {
this.wait(); // 当value非空时等待
} catch (InterruptedException e) {
}
}
value = i; // 往池子放入产品
System.out.println("Sender put :" + i);
isEmpty = false; // 表明池子有产品了
notify(); // 唤醒其他等待线程,此处用notify和notifyAll均可以,因为只有1个消费者和1个生产者,交替唤醒
}
public synchronized int get() {
if (isEmpty) {
try {
this.wait(); // 当value空时等待
} catch (InterruptedException e) {
}
}
isEmpty = true; // 表明池子没有产品了
System.out.println("Receiver get :" + value);
notify(); // 唤醒其他等待线程,此处用notify和notifyAll均可以,因为只有1个消费者和1个生产者,交替唤醒
return value;
}
public static void main(String[] args) {
BufferLock buffer = new BufferLock();
new Sender(buffer).start(); // 启动一个生产者线程
new Receiver(buffer).start(); // 启动一个消费者线程
}
}
class Sender extends Thread {
// 发送线程类
private BufferLock buffer;
public Sender(BufferLock buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 6; i++) {
// 发送6个数字
buffer.put(i);
}
}
}
class Receiver extends Thread {
// 接收线程类
private BufferLock buffer;
public Receiver(BufferLock buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 6; i++) {
// 接收6次
buffer.get();
}
}
}
执行结果:
Sender put :0 //生产一个
Receiver get :0 //消费一个
Sender put :1
Receiver get :1
Sender put :2
Receiver get :2
Sender put :3
Receiver get :3
Sender put :4
Receiver get :4
Sender put :5
Receiver get :5
在put()方法中,先检查信号量状态,如果池子满了,则阻塞。否则放入一个产品,通知被阻塞的消费者线程。
在get()方法中,先检查信号量状态,如果池子为空,则阻塞。否则取出一个产品,通知被阻塞的生产者线程。
生产线程发送6个数字,消费线程收到6个数字,并且发送和接收的值都能匹配上。
一个生产线程2个消费线程
我们改进一下上面的例子,首先我们准备一个生产线程和2个消费线程,前者共生产12个数字,我们期望能消费到全部的12个数字。
public class BufferLock2 {
private int value;
/**
* value是否为空的信令号量,可以理解存储产品的池子,容量只有1<br>
* 为空表示池子空了,可以放入数据,不可以取数据;<br>
* 非空表示池子满了,不可以放入数据,可以取数据
*/
private boolean isEmpty = true;
public synchronized void put(int i) {
while (!isEmpty) {
try {
this.wait(); // 当value非空时等待
} catch (InterruptedException e) {
}
}
value = i; // 往池子放入产品
System.out.println("Sender put :" + i);
isEmpty = false;
notifyAll(); // 唤醒其他等待线程
}
public synchronized int get() {
while (isEmpty) {
try {
this.wait(); // 当value空时等待
} catch (InterruptedException e) {
}
}
isEmpty = true;
System.out.println("Receiver get :" + value);
notifyAll(); // 唤醒其他等待线程
return value;
}
public static void main(String[] args) {
BufferLock2 buffer = new BufferLock2();
new Sender2(buffer).start();
new Receiver2(buffer).start();
new Receiver2(buffer).start();
}
}
class Sender2 extends Thread {
// 发送线程类
private BufferLock2 buffer;
public Sender2(BufferLock2 buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 12; i++) {
// 发送12个数字
buffer.put(i);
}
}
}
class Receiver2 extends Thread {
// 接收线程类
private BufferLock2 buffer;
public Receiver2(BufferLock2 buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 6; i++) {
// 接收6次
buffer.get();
}
}
}
执行结果:
Sender put :0 //依次交替
Receiver get :0
Sender put :1
Receiver get :1
Sender put :2
Receiver get :2
Sender put :3
Receiver get :3
Sender put :4
Receiver get :4
Sender put :5
Receiver get :5
Sender put :6
Receiver get :6
Sender put :7
Receiver get :7
Sender put :8
Receiver get :8
Sender put :9
Receiver get :9
Sender put :10
Receiver get :10
Sender put :11
Receiver get :11
我们对比下这2段代码,发现主要的区别:
- while (!isEmpty)
原因是现在是3个线程,使用notify或notifyAll的话,不一定是生产和消费交替,而有可能是生产线程>消费线程>消费线程
,由于连续唤醒的是消费线程,导致连续消费了两次,即有的值被读了两遍;while 作用是当连续唤醒的是消费线程时,不断的wait,直至下一次是生产线程。
读者可以替换为if语句,执行结果可能为:
Sender put :0
Receiver get :0 //消费线程
Receiver get :0 //连续消费线程,同一个值被消费多遍
Receiver get :0
Sender put :1
Receiver get :1
Receiver get :1
Receiver get :1
Sender put :2
Receiver get :2
Receiver get :2
Receiver get :2
Sender put :3
Receiver get :3
Sender put :4
Receiver get :4
Sender put :5
Receiver get :5
Sender put :6