生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况:存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁。
一、wait() / notify()方法
Java 中,可以通过配合调用 Object 对象的 wait() 方法和 notify()方法或 notifyAll() 方法来实现线程间的通信。在线程中调用 wait() 方法,将阻塞当前线程,直至等到其他线程调用了 notify() 方法或 notifyAll() 方法进行通知之后,当前线程才能从wait()方法出返回,继续执行下面的操作。
package com.study.thread;
import java.util.LinkedList;
class ShareDataSync {
private int maxNum = 10;
private LinkedList<String> list = new LinkedList<>();
//生产者
public void produceData() {
synchronized (list) {
try {
while (list.size() >= maxNum) {
//仓库已满,等待被消费
list.wait();
}
list.add("1");
System.out.println(Thread.currentThread().getName() + "生产者,剩余库存=" + list.size());
list.notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//消费者
public void consumeData() {
synchronized (list) {
try {
while (list.size() <= 0) {
//等待数据被生产
list.wait();
}
list.remove(0);
System.out.println(Thread.currentThread().getName() + "消费者,剩余库存=" + list.size());
list.notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class ProductConsumeSynchorizeDemo {
public static void main(String[] args) {
ShareDataSync shareData = new ShareDataSync();
//生产
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.produceData();
}
}, "AA").start();
//消费
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.consumeData();
}
}, "BB").start();
//生产
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.produceData();
}
}, "CC").start();
//消费
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.consumeData();
}
}, "DD").start();
}
}
运行结果:
AA生产者,剩余库存=1
AA生产者,剩余库存=2
AA生产者,剩余库存=3
AA生产者,剩余库存=4
AA生产者,剩余库存=5
BB消费者,剩余库存=4
BB消费者,剩余库存=3
BB消费者,剩余库存=2
BB消费者,剩余库存=1
BB消费者,剩余库存=0
CC生产者,剩余库存=1
CC生产者,剩余库存=2
CC生产者,剩余库存=3
CC生产者,剩余库存=4
CC生产者,剩余库存=5
DD消费者,剩余库存=4
DD消费者,剩余库存=3
DD消费者,剩余库存=2
DD消费者,剩余库存=1
DD消费者,剩余库存=0
注意:
notifyAll()方法可使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的哪个线程最先执行,但也有可能是随机执行的,这要取决于JVM虚拟机的实现。即最终也只有一个线程能被运行,上述线程优先级都相同,每次运行的线程都不确定是哪个,后来给线程设置优先级后也跟预期不一样,还是要看JVM的具体实现吧。
二、await() / signal()方法
在JDK5中,用ReentrantLock和Condition可以实现等待/通知模型,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。
package com.study.thread;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData {
private int maxNum = 10;
private LinkedList<String> list = new LinkedList<>();
Lock lock = new ReentrantLock();
Condition full = lock.newCondition();
Condition empty = lock.newCondition();
//生产者
public void produceData() {
lock.lock();
try {
while (list.size() >= maxNum) {
//仓库已满,等待被消费
full.await();
}
list.add("1");
System.out.println(Thread.currentThread().getName() + "生产者,剩余库存=" + list.size());
empty.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//消费者
public void consumeData() {
lock.lock();
try {
while (list.size() <= 0) {
//等待数据被生产
empty.await();
}
list.remove(0);
System.out.println(Thread.currentThread().getName() + "消费者,剩余库存=" + list.size());
full.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProductConsumeLockDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
//生产
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.produceData();
}
}, "AA").start();
//消费
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.consumeData();
}
}, "DD").start();
//消费
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.consumeData();
}
}, "BB").start();
//生产
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.produceData();
}
}, "CC").start();
}
}
运行结果:
AA生产者,剩余库存=1
AA生产者,剩余库存=2
CC生产者,剩余库存=3
CC生产者,剩余库存=4
CC生产者,剩余库存=5
CC生产者,剩余库存=6
CC生产者,剩余库存=7
DD消费者,剩余库存=6
DD消费者,剩余库存=5
DD消费者,剩余库存=4
DD消费者,剩余库存=3
DD消费者,剩余库存=2
BB消费者,剩余库存=1
BB消费者,剩余库存=0
AA生产者,剩余库存=1
AA生产者,剩余库存=2
AA生产者,剩余库存=3
BB消费者,剩余库存=2
BB消费者,剩余库存=1
BB消费者,剩余库存=0
三、BlockingQueue阻塞队列方法
BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,底层实现方式采用的是我们第2种await() / signal()方法。它可以在生成对象时指定容量大小,用于阻塞操作的是put()、offer()和take()、poll()方法。
package com.study.thread;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
class ShareDataBlockQueue {
private boolean flag = true;
//保证多线程下生产者的线程安全
private AtomicInteger atomicInteger = new AtomicInteger(0);
BlockingQueue blockingQueue = null;
public ShareDataBlockQueue(BlockingQueue blockingQueue) {
this.blockingQueue = blockingQueue;
}
//生产者
public void produceData() throws InterruptedException {
while (flag) {
int i = atomicInteger.incrementAndGet();
boolean offer = blockingQueue.offer(i, 3, TimeUnit.SECONDS);
if (offer) {
System.out.println(Thread.currentThread().getName() + "生产" + i);
} else {
System.out.println(Thread.currentThread().getName() + "生产失败" + i);
}
Thread.sleep(1000);
}
}
//消费者
public void consumeData() throws InterruptedException {
while (flag) {
Object poll = blockingQueue.poll(2, TimeUnit.SECONDS);
if (poll == null || poll.equals("")) {
flag = false;
System.out.println("超过2秒没有取到数据,消费退出");
return;
} else {
System.out.println(Thread.currentThread().getName() + "消费" + poll);
}
}
}
public void stop() {
this.flag = false;
}
}
public class ProductConsumeBlockQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue(10);
ShareDataBlockQueue shareData = new ShareDataBlockQueue(blockingQueue);
//生产
new Thread(() -> {
try {
shareData.produceData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AA").start();
new Thread(() -> {
try {
shareData.produceData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BB").start();
//消费
new Thread(() -> {
try {
shareData.consumeData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "CC").start();
//消费
new Thread(() -> {
try {
shareData.consumeData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "DD").start();
Thread.sleep(5000);
System.out.println("mian进程10s后停止生产,队列大小:" + shareData.blockingQueue.size());
shareData.stop();
}
}
运行结果:
AA生产1
CC消费1
CC消费2
BB生产2
BB生产3
DD消费4
CC消费3
AA生产4
AA生产5
CC消费6
BB生产6
DD消费5
AA生产7
DD消费8
BB生产8
CC消费7
AA生产9
CC消费10
BB生产10
DD消费9
mian进程10s后停止生产,队列大小:0
超过2秒没有取到数据,消费退出
超过2秒没有取到数据,消费退出
四、信号量
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。计数为0的Semaphore是可以release的,release之后加1,然后就可以acquire,acquire之后减1
package com.study.thread;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class ShareDataSemaphore {
//可以生产的总数量。 通过生产者调用acquire,减少permit数目
Semaphore canProduceCount = new Semaphore(10);
//互斥锁 互斥量,控制共享数据的互斥访问
Semaphore block = new Semaphore(1);
//可以消费的数量。通过生产者调用release,增加permit数目
Semaphore canConsumerCount = new Semaphore(0);
private LinkedList list = new LinkedList<>();
//生产者
public void produceData() throws InterruptedException {
try {
// 可生产数量 -1
canProduceCount.acquire();
//加锁
block.acquire();
list.add("1");
System.out.println(Thread.currentThread().getName() + "生产者,仓库大小" + list.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
block.release();
//能消费的数量 +1
canConsumerCount.release();
}
}
//消费者
public void consumeData() throws InterruptedException {
try {
//能消费的数量 -1
canConsumerCount.acquire();
//加锁
block.acquire();
list.remove();
System.out.println(Thread.currentThread().getName() + "消费者,仓库大小" + list.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
block.release();
//能生产的数量 +1
canProduceCount.release();
}
}
}
public class ProductConsumeSemaphoreDemo {
public static void main(String[] args) throws InterruptedException {
ShareDataSemaphore shareData = new ShareDataSemaphore();
//生产
new Thread(() -> {
try {
shareData.produceData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AA").start();
new Thread(() -> {
try {
shareData.produceData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BB").start();
//消费
new Thread(() -> {
try {
shareData.consumeData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "CC").start();
//消费
new Thread(() -> {
try {
shareData.consumeData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "DD").start();
// Thread.sleep(5000);
// shareData.stop();
}
}