場景
生產者生產商品存放在工廠中,消費者從工廠中取出商品。工廠最多隻能存放一件商品。
一個生產者一個消費者
public class ProductFactory {
private List<String> goods = new ArrayList<>();
private static int index;
synchronized public void add(){
try{
while(goods.size() == 1){
this.wait();
}
goods.add("商品" + ++index);
this.notify();
System.out.println("add" + goods.get(0));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String del(){
try{
if(goods.size() == 0){
this.wait();
}
value = goods.get(0);
goods.remove(0);
this.notify();
System.out.println("del" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}
}
這裏使用List對象來存放數據, produce方法和consume方法使用synchronized進行同步。
public class Producer {
private ProductFactory factory;
public Producer(ProductFactory factory){
this.factory = factory;
}
public void produce(){
factory.add();
}
}
public class Consumer {
private ProductFactory factory;
public Consumer(ProductFactory factory){
this.factory = factory;
}
public void consume(){
factory.del();
}
}
生產者和消費者的線程代碼
class ThreadP extends Thread{
private Producer producer;
public ThreadP(Producer producer){
this.producer = producer;
}
@Override
public void run() {
while(true)
producer.produce();
}
}
class ThreadC extends Thread{
private Consumer consumer;
public ThreadC(Consumer consumer){
this.consumer = consumer;
}
@Override
public void run() {
while(true)
consumer.consume();
}
}
運行類的代碼
public class TestProducerConsumer {
public static void main(String[] args) {
ProductFactory factory = new ProductFactory();
Producer producer = new Producer(factory);
ThreadP threadP = new ThreadP(producer);
Consumer consumer = new Consumer(factory);
ThreadC threadC = new ThreadC(consumer);
threadP.start();
threadC.start();
return;
}
}
程序的輸出:
...
add商品24265
del商品24265
add商品24266
del商品24266
add商品24267
del商品24267
...
可以看到程序的結果是生產者先生產一個商品,然後消費者消費一個商品,也就是生產者和消費者在交替的運行。
一個生產者多個消費者
我們修改運行類的代碼
public class TestProducerConsumer {
public static void main(String[] args) {
ProductFactory factory = new ProductFactory();
Producer producer = new Producer(factory);
ThreadP threadP = new ThreadP(producer);
Consumer consumer = new Consumer(factory);
Consumer consumer1 = new Consumer(factory);
Consumer consumer2 = new Consumer(factory);
Consumer consumer3 = new Consumer(factory);
ThreadC threadC = new ThreadC(consumer);
ThreadC threadC1 = new ThreadC(consumer1);
ThreadC threadC2 = new ThreadC(consumer2);
ThreadC threadC3 = new ThreadC(consumer3);
threadP.start();
threadC.start();
threadC1.start();
threadC2.start();
threadC3.start();
return;
}
}
程序的輸出
add商品1
Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
del商品1
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
add商品2
del商品2
at java.util.ArrayList.get(ArrayList.java:433)
at multithread.producerconsumer.ProductFactory.del(ProductFactory.java:39)
at multithread.producerconsumer.Consumer.consume(Consumer.java:18)
at multithread.producerconsumer.ThreadC.run(TestProducerConsumer.java:105)
我們分析爲什麼會發生消費者數組越界的異常,也就是爲什麼在判斷了goods.size() == 0
之後,消費者線程調用了wait方法,線程被掛起了之後,消費者線程還執行了goods.remove(0)
。
假設目前商品的數量爲1,消費者線程將會執行
value = goods.get(0);
goods.remove(0);
this.notify();
System.out.println("del" + value);
當前消費者線程首先刪除goods中僅有的一個元素,此時goods已經爲空。緊接着調用notify()方法,該消費者線程會在執行完該同步方法剩下的語句之後,釋放該對象鎖。等待該對象鎖的線程出了生產者線程,還有其他幾個消費者線程。由於notify會隨機讓一個等待對象鎖的線程執行,所以很有可能被選中的是一個消費者線程。
如果選中的是另一個消費者線程,那麼會從該消費者線程wait()方法接着往下執行。
if(goods.size() == 0){
this.wait();
}
value = goods.get(0);
goods.remove(0);
this.notify();
System.out.println("del" + value);
也就是離開if語句,再次執行了goods.remove(0)
,所以發生了數組越界。
這裏發生問題的地方就是一個消費者線程喚醒了另一個消費者線程,而對goods大小的判斷卻沒有起到作用。所以要修改這個問題,可以把消費者線程判斷goods大小的語句if替換成while。
public class ProductFactory {
private List<String> goods = new ArrayList<>();
private static int index;
synchronized public void add(){
try{
if(goods.size() == 1){
this.wait();
}
goods.add("商品" + ++index);
this.notify();
// this.notifyAll();
System.out.println("add" + goods.get(0));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String del(){
String value = "";
try{
while(goods.size() == 0){
this.wait();
}
value = goods.get(0);
goods.remove(0);
this.notify();
System.out.println("del" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}
}
修改後運行代碼,結果是
add商品1
del商品1
add商品2
del商品2
這次程序雖然沒有出現數組越界異常,但是程序卻出現了假死。
我們將if替換成while,雖然可以避免發生異常,但是消費者喚醒消費者的現象卻依然存在。程序出現假死的主要原因也就是連續喚醒同類。那麼只要不光喚醒同類線程,也將異類線程一起喚醒就可以解決了,也就是利用notifyAll()方法。
public class ProductFactory {
private List<String> goods = new ArrayList<>();
private static int index;
synchronized public void add(){
try{
if(goods.size() == 1){
this.wait();
}
goods.add("商品" + ++index);
// this.notify();
this.notifyAll();
System.out.println("add" + goods.get(0));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String del(){
String value = "";
try{
while(goods.size() == 0){
this.wait();
}
value = goods.get(0);
goods.remove(0);
// this.notify();
this.notifyAll();
System.out.println("del" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}
}
將兩個同步方法中的notify方法替換成notifyAll方法,運行程序
...
add商品49834
del商品49834
add商品49835
del商品49835
add商品49836
del商品49836
...
可以看到程序正常運行了。
多個生產者一個消費者
我們讓ProductFactory的代碼回到開始的轉態,也就是沒有使用while和notifyAll的狀態,
public class ProductFactory {
private List<String> goods = new ArrayList<>();
private static int index;
synchronized public void add(){
try{
if(goods.size() == 1){
this.wait();
}
goods.add("商品" + ++index);
this.notify();
// this.notifyAll();
System.out.println("add" + goods.get(0));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String del(){
String value = "";
try{
if(goods.size() == 0){
this.wait();
}
value = goods.get(0);
goods.remove(0);
this.notify();
// this.notifyAll();
System.out.println("del" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}
}
把運行類的代碼修改爲多個生產者和一個消費者
public class TestProducerConsumer {
public static void main(String[] args) {
ProductFactory factory = new ProductFactory();
Producer producer = new Producer(factory);
ThreadP threadP = new ThreadP(producer);
Consumer consumer = new Consumer(factory);
ThreadC threadC = new ThreadC(consumer);
Producer producer1 = new Producer(factory);
Producer producer2 = new Producer(factory);
Producer producer3 = new Producer(factory);
ThreadP threadP1 = new ThreadP(producer1);
ThreadP threadP2 = new ThreadP(producer2);
ThreadP threadP3 = new ThreadP(producer3);
threadP.start();
threadP1.start();
threadP2.start();
threadP3.start();
threadC.start();
return;
}
}
運行結果:
...
del商品30718
del商品30719
del商品30720
add商品30721
add商品30721
add商品30721
add商品30721
...
從程序的輸出我們看到生產者和消費者線程不是交替執行的。按照我們的設想應該是生產者線程生產一個商品,然後消費者線程將這個商品消費掉。
出現這個問題的原因,和上面一個生產者多個消費者問題的原因是一樣的。由於在同步方法中使用的是if判斷goods的大小,生產者可能會被生產者喚醒,喚醒後接着執行if後面的語句。解決這個問題將if緩存while就可以了。
僅僅將if緩存while同樣會遇到程序假死的問題,問題的原因上面已經解釋過了,還是要把notify方法替換成notifyAll方法。
多個生產者多個消費者
根據上面的分析,多個生產者和多個消費者的代碼就很容易了,只需要注意:
1. 同步方法中使用while而不是if來判斷條件;
2. 使用notifyAll方法喚醒所以的線程。
public class ProductFactory {
private List<String> goods = new ArrayList<>();
private static volatile int index;
synchronized public void add(){
try{
while(goods.size() == 1){
this.wait();
}
goods.add("商品" + ++index);
// this.notify();
this.notifyAll();
System.out.println("add" + goods.get(0));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public String del(){
String value = "";
try{
while(goods.size() == 0){
this.wait();
}
value = goods.get(0);
goods.remove(0);
// this.notify();
this.notifyAll();
System.out.println("del" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}
}
運行類
public class TestProducerConsumer
public static void main(String[] args) {
ProductFactory factory = new ProductFactory();
Producer producer = new Producer(factory);
ThreadP threadP = new ThreadP(producer);
Consumer consumer = new Consumer(factory);
Consumer consumer1 = new Consumer(factory);
Consumer consumer2 = new Consumer(factory);
Consumer consumer3 = new Consumer(factory);
ThreadC threadC = new ThreadC(consumer);
ThreadC threadC1 = new ThreadC(consumer1);
ThreadC threadC2 = new ThreadC(consumer2);
ThreadC threadC3 = new ThreadC(consumer3);
Producer producer1 = new Producer(factory);
Producer producer2 = new Producer(factory);
Producer producer3 = new Producer(factory);
ThreadP threadP1 = new ThreadP(producer1);
ThreadP threadP2 = new ThreadP(producer2);
ThreadP threadP3 = new ThreadP(producer3);
threadC.start();
threadC1.start();
threadC2.start();
threadC3.start();
threadP.start();
threadP1.start();
threadP2.start();
threadP3.start();
return;
}
}