生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
有时,当线程进入synchronized块后,共享数据的状态并不满足它的需要,它要等待其他线程将共享数据改变为它需要的状态后才能执行。但由于此时它占据了该对象的锁,其他线程无法对共享数据进行操作。这时,我们可以用wait()和notify()这两个方法实现线程间的通信。
如果线程调用了某个对象X的wait()方法——X.wait(),则该线程将放入X的wait pool,并且该线程将释放X的锁;当线程调用X的notify()方法——X.notify()时,则将会使对象X的wait pool中的一个线程移入到lock pool,在lock pool中等待X的锁,一旦获得便可执行。notifyAll()可以把对象wait pool中的所有线程都移入lock pool。我们可以使用wait()和notify()实现线程的同步。
当某个线程需要在synchronized块中等待共享数据状态改变时,可以调用wait()方法,这样使该线程等待并且暂时释放共享数据对象的锁,其他线程可以获得该对象的锁并进入synchronized块操作共享数据。当其操作完毕后,调用notify()方法唤醒正在等待的线程重新占有锁并运行。
在系统中,使用某类资源的线程一般称为消费者,产生或释放同类资源的线程称为生产者。生产者-消费者问题是关于线程交互与同步问题的一般模型。
案例演示如下:
利用线程交互, 完成连续打印1-100的数字, 一条线程专门打印奇数,另外一条专门打印偶数。
方法一:
public class Test {
public static void main(String[] args) {
//打印奇数的线程
new Thread(){
@Override
public void run() {
while (true){
synchronized (Number.lock){
if(Number.number > 100){
break;
}
if(Number.number % 2 != 0){
System.out.println("奇数线程打印" + Number.number++);
Number.lock.notify();
}else {
try {
Number.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}.start();
//打印偶数的线程
new Thread() {
@Override
public void run() {
while (true){
synchronized (Number.lock) {
if (Number.number > 100) {
break;
} else {
if (Number.number % 2 == 0) {
System.out.println("偶数线程打印" + Number.number++);
Number.lock.notify();
} else {
try {
Number.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}.start();
}
}
class Number{
//共享数据
public static int number = 1;
//锁对象
public static Object lock = new Object();
}
方法二:
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable, "奇数").start();
new Thread(myRunnable, "偶数").start();
}
}
class MyRunnable implements Runnable{
//共享数据
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this){
if(number > 100){
break;
}
String name = Thread.currentThread().getName();
if(("奇数".equals(name) && number % 2 == 0) ||
("偶数".equals(name) && number % 2 != 0)){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(name + "线程打印" + number++);
this.notify();
}
}
}
}
}