前一篇文章講了如何使用java5中的重入鎖和條件變量優雅地解決生產者消費者問題,本文將繼續探究java併發包(concurrent),尋求更好的解決方案。
java併發包中提供了阻塞隊列(BlockingQueue),查看該類的註釋說明,大概翻譯下:這是一個額外支持等待隊列非空或等待隊列可用的隊列,意思就是當線程去隊列取元素時,如果沒有元素線程將等待,直到有可用的有元素可取;當向隊列中添加元素時,判斷隊列是否已滿,如果已滿,則線程等待,直到隊列可以添加元素。這樣一看,阻塞隊列完全可以解決生產者消費者問題。代碼如下:
Consumer.java
package CreatorAndConsumerBlock;
public class Consumer implements Runnable {
/**
* 線程資源
*/
private Plate plate;
public Consumer(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
plate.getEgg();
}
}
Creator.javapackage CreatorAndConsumerBlock;
/**
* 生產者
*
* @author Martin
*/
public class Creator implements Runnable {
/**
* 線程資源
*/
private Plate plate;
public Creator(Plate plate) {
this.plate = plate;
}
@Override
public void run() {
Object egg = new Object();
plate.addEgg(egg);
}
}
Plate.javapackage CreatorAndConsumerBlock;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 盤子,表示共享的資源
*
* @author Martin
*/
public class Plate {
/**
* 將arraylist換成阻塞隊列,可見使用了阻塞隊列後,代碼中再也沒有了lock、condition等了,完全不需要手動控制線程的等待還是喚醒了
*/
private ArrayBlockingQueue<Object> eggs = new ArrayBlockingQueue<Object>(99999);
/**
* 獲取蛋
*
* @return
*/
public Object getEgg() {
try {
Object egg = eggs.take();
System.out.println("消費者取蛋,當前剩餘:" + eggs.size());
return egg;
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
/**
* 加入蛋
*
* @return
*/
public void addEgg(Object egg) {
try {
eggs.put(new Object());
System.out.println("生產者生蛋,當前剩餘:" + eggs.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Tester.javapackage CreatorAndConsumerBlock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Tester {
public static void main(String[] args)
{
//共享資源
Plate plate = new Plate();
ExecutorService pool = Executors.newFixedThreadPool(100);
//添加生產者和消費者
for(int i = 0 ; i < 100000; i ++)
{
pool.execute(new Creator(plate));
pool.execute(new Consumer(plate));
}
pool.shutdown();
}
}
和使用synchronized關鍵字和重入鎖、條件變量相比,代碼大大簡化,程序中完全不需要關係線程如何同步資源,什麼時候喚醒其他線程。其實,我們查看阻塞隊列的源碼,其實其也是使用重入鎖、條件變量來實現的。