我们先来看一组例子
package com.buerc.thread;
public class TesProducerAndConsumer {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer producer=new Producer(clerk);
Consumer consumer=new Consumer(clerk);
new Thread(producer,"生产者A1").start();
new Thread(consumer,"消费者B1").start();
}
}
class Clerk{//店员类
private static final int TOTAL=1;//数字取1是为了放大问题
private int num=0;
public synchronized void get() {//店员买货
if(num>=TOTAL) {
System.out.println("库存已满,无法进货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : "+ (num++));
this.notifyAll();
}
}
public synchronized void sale() {//店员卖货
if(num<=0) {
System.out.println("库存已空,无法卖货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+" : "+(num--));
this.notifyAll();
}
}
}
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
try {
Thread.sleep(200);//放大问题
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
clerk.sale();
}
}
}
这样看起来似乎很合理。一个生产者生产,一个消费者消费,但是运行起来却是控制台没停。而我们这里是for20次循环,按理来说程序最终会终止,可情况恰恰相反。问题产生的根源是,由于生产者现象睡眠了200毫秒,因而可能产生的情况是最后消费者线程循环走完了然后就真的结束了,而生产者线程由于wait没有线程来唤醒,所以最终导致一直等待,因而程序不会结束,控制台就不终止。解决方法:
package com.buerc.thread;
public class TesProducerAndConsumer {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer producer=new Producer(clerk);
Consumer consumer=new Consumer(clerk);
new Thread(producer,"生产者A1").start();
new Thread(consumer,"消费者B1").start();
}
}
class Clerk{//店员类
private static final int TOTAL=1;//数字取1是为了放大问题
private int num=0;
public synchronized void get() {//店员买货
if(num>=TOTAL) {
System.out.println("库存已满,无法进货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : "+ (num++));
this.notifyAll();
}
public synchronized void sale() {//店员卖货
if(num<=0) {
System.out.println("库存已空,无法卖货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : "+(num--));
this.notifyAll();
}
}
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
try {
Thread.sleep(200);//放大问题
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
clerk.sale();
}
}
}
这里把同步方法的else去掉了,那么无论最终哪个线程先走完,都会执行wait后面的方法,即它在结束前会唤醒等待的线程,因而这个线程最终也会完整的执行完,最后程序终止。这种方法的确解决了程序无法终止的问题,但是我们来看看,当我们多加一对消费者线程和生产者线程时。
package com.buerc.thread;
public class TesProducerAndConsumer {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer producer=new Producer(clerk);
Consumer consumer=new Consumer(clerk);
Producer producer2=new Producer(clerk);
Consumer consumer2=new Consumer(clerk);
new Thread(producer,"生产者A1").start();
new Thread(consumer,"消费者B1").start();
new Thread(producer2,"生产者A2").start();
new Thread(consumer2,"消费者B2").start();
}
}
class Clerk{//店员类
private static final int TOTAL=1;//数字取1是为了放大问题
private int num=0;
public synchronized void get() {//店员买货
if(num>=TOTAL) {
System.out.println("库存已满,无法进货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : "+ (num++));
this.notifyAll();
}
public synchronized void sale() {//店员卖货
if(num<=0) {
System.out.println("库存已空,无法卖货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : "+(num--));
this.notifyAll();
}
}
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
try {
Thread.sleep(200);//放大问题
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
clerk.sale();
}
}
}
这种情况出现了产品数为负的情况,肯定不合适。这就是虚假唤醒,因为有可能num==0,然后两个消费者线程都wait,此时生产者执行num++后,在唤醒却是唤醒了所有等待的线程,此时这两个消费者线程抢占资源后立马执行wait之后的操作,即num--就会出现产品为负的情况。为了避免这种情况我们应该让wait()在while循环中。(API上关于虚假唤醒的解决方式)
package com.buerc.thread;
public class TesProducerAndConsumer {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer producer=new Producer(clerk);
Consumer consumer=new Consumer(clerk);
Producer producer2=new Producer(clerk);
Consumer consumer2=new Consumer(clerk);
new Thread(producer,"生产者A1").start();
new Thread(consumer,"消费者B1").start();
new Thread(producer2,"生产者A2").start();
new Thread(consumer2,"消费者B2").start();
}
}
class Clerk{//店员类
private static final int TOTAL=1;//数字取1是为了放大问题
private int num=0;
public synchronized void get() {//店员买货
while(num>=TOTAL) {
System.out.println("库存已满,无法进货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : "+ (num++));
this.notifyAll();
}
public synchronized void sale() {//店员卖货
while(num<=0) {
System.out.println("库存已空,无法卖货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" : "+(num--));
this.notifyAll();
}
}
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
try {
Thread.sleep(200);//放大问题
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for (int i = 0; i<20; i++) {
clerk.sale();
}
}
}