定義
- 生產者持續生產,直到緩衝區滿,阻塞;緩衝區不滿後,繼續生產
- 消費者持續消費,直到緩衝區空,阻塞;緩衝區不空後,繼續消費
- 生產者可以有多個,消費者也可以有多個
實現手段
LinkedBlockingQueue/wait,notify/Lock,Condition
面向介面程式設計,抽象生產消費
public interface Consumer {
void consume() throws InterruptedException;
}
public interface Produce {
void produce() throws InterruptedException;
}
// 可以省略上面介面,直接定義此抽象類。
abstract class AbsConsumer implements Consumer, Runnable {
@Override
public void run () {
try {
consume();
} cath (InterruptedException) {
}
}
}
abstract class AbsProducer implements Consumer, Runnable {
@Override
public void run () {
try {
consume();
} cath (InterruptedException) {
}
}
}
不同的Model,會有不同的生產,消費實現。我們可以再抽象下model。
當然也可以不,在一個model裡面實現。
public interface Model {
Runnable newRunnableConsumer();
Runnable newRunnableProducer();
}
複製程式碼
定義消費單位
public class Task {
public int no;
public Task(int no) {
this.no = no;
}
}
複製程式碼
具體生產消費程式碼
// 只要改變下具體的實現Model就可以了。
// 這裡完全可以用一個策略模式,動態設定model。
// 但這是測試生產消費程式碼,就不要那麼正式了。
Model model = new BlockingQueueModel(3);
for (int i = 0; i < 2; i++) {
new Thread(model.newRunnableConsumer()).start();
}
for (int i = 0; i < 5; i++) {
new Thread(model.newRunnableProducer()).start();
}
複製程式碼
LinkedBlockingQueue
這是天生的一個為我們生產消費實現的資料結構。它提供了併發,容量的實現。接下來,一波程式碼
public class BlockingQueueModel implements Model {
//儲存我們的Task
private final BlockingQueue<Task> queue;
// 生產Task,原子性保證每個Task的標識在併發下的準確性,互相不衝突
private final AtomicInteger increTaskNo = new AtomicInteger(0);
// 構造方法提供容量控制引數
public BlockingQueueModel(int cap) {
// 使用LinkedBlockingQueue和ArrayBlocingQueue操作效率上沒有太大差別
this.queue = new LinkedBlockingQueue<>(cap);
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl extends AbstractConsumer{
@Override
public void consume() throws InterruptedException {
Thread.sleep((long) (Math.random() * 1000));
Task task = queue.take();
System.out.println("consume: " + task.no);
}
}
private class ProducerImpl extends AbstractProducer{
@Override
public void produce() throws InterruptedException {
Thread.sleep((long) (Math.random() * 1000));
Task task = new Task(increTaskNo.getAndIncrement());
System.out.println("produce: " + task.no);
queue.put(task);
}
}
}
複製程式碼
wait,notify
public class WaitNotifyModel implements Model {
// object去提供鎖
private final Object BUFFER_LOCK = new Object();
// 儲存Task,用object保證併發性
private final Queue<Task> buffer = new LinkedList<>();
private final int cap;
private final AtomicInteger increTaskNo = new AtomicInteger(0);
public WaitNotifyModel(int cap) {
this.cap = cap;
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl extends AbstractConsumer{
@Override
public void consume() throws InterruptedException {
synchronized (BUFFER_LOCK) {
// 緩衝區為空,阻塞。這種阻塞屬於等待阻塞。
while (buffer.size() == 0) {
BUFFER_LOCK.wait();
}
// 緩衝區的操作得到synchronized的同步保證。
Task task = buffer.poll();
Thread.sleep((Math.random() * 1000));
System.out.println("consume: " + task.no);
BUFFER_LOCK.notifyAll();
}
}
}
private class ProducerImpl extends AbstractProducer{
@Override
public void produce() throws InterruptedException {
Thread.sleep((long) (Math.random() * 1000));
synchronized (BUFFER_LOCK) {
while (buffer.size() == cap) {
BUFFER_LOCK.wait();
}
Task task = new Task(increTaskNo.getAndIncrement());
buffer.offer(task);
System.out.println("produce: " + task.no);
BUFFER_LOCK.notifyAll();
}
}
}
}
複製程式碼
synchronized控制produce,consume併發,wait控制緩衝區臨界阻塞。 所以,你會看到produce,consume其實是序列的。同一時間,要麼是在produce,要麼是在consume。那麼,問題來了:為什麼不可以並行?通過什麼方案可以實現produce,consume的並行並且可以保證緩衝區臨界值的準確控制?
Lock,Condition
對於wait,notify的問題,在我們的Lock,Condition這邊可以得到解決!其實,參考的就是LinkedBlockingQueue的實現。
public class LockConditionModel implements Model{
private final int cap;
// 讀寫鎖分離。
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
// 這個保證臨界區的值
private final AtomicInteger count = new AtomicInteger();
private Queue<Task> buffer = new LinkedList<>();
public LockConditionModel(int cap) {
this.cap = cap;
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl extends AbstractConsumer{
@Override
public void consume() throws InterruptedException {
Task x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
// 這個while不可以換成if。
// 假設有兩個consume執行緒卡在這裡。前一個獲取鎖,操作後count為零,那麼下一個如果不再次判斷就出問題啦
while (count.get() == 0) {
notEmpty.await();
}
x = buffer.poll();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
}
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
private class ProducerImpl extends AbstractProducer {
@Override
public void produce() throws InterruptedException {
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
Task task = buffer.poll();
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
}
複製程式碼
程式碼已完整,文字描述待續。。。