面试时可能会面到生产者消费者模式,我们用java代码来实现下,一步一步思路给大家列出来,方便理解记忆
单生产者消费者模式
- 先想象一下,我们要弄一个仓库,这个仓库有两个暴露出去的方法put 和 pull ,一个是将产品放入(生产)自己的仓库,一个是从自己的仓库消费(取出)一个,还有一个属性count,表示仓库的容量
到这里请用自己代码实现下上面的功能,再看下面的,下面同理
下面给出上面的实现
public class Factory {
int count = 0;
public void put() {
count++;
}
public void pull() {
count--;
}
}
- 很简单吧,下面再来想一下,在我们count++,和count–的时候,消费者和生产者同时对count进行操作,所以会有线程安全问题,加锁
public class Factory {
int count = 0;
public void put() {
synchronized (this) {
count++;
}
}
public void pull() {
synchronized (this) {
count--;
}
}
}
这样同时只会有一个线程去操作我们的数据了,保证了安全,再来想
3. 我们的仓库有一个最大容量,因为生产着放满了就得停止,让消费者继续消费,消费者消费到0的时候就该停止,再让生产者生产,我们用wait方法来模拟生生产满了或者消费完了之后进行的停止操作,notifyAll()来模拟生产满了之后让消费者醒来去消费的操作,最大容量设置为10,之后我们的代码是
public class Factory {
int count = 0;
public void put() {
synchronized (this) {
//判断是否满了
if(count == 10){
//在停止之前唤醒所有等待的线程
notifyAll();
//停止生产,进入等待状态
wait();
}
count++;
}
}
public void pull() {
synchronized (this) {
//判断是否满了
if(count == 10){
//在停止之前唤醒所有等待的线程
notifyAll();
//停止生产,进入等待状态
wait();
}
count--;
}
}
}
- 大家,至此我们的简单的生产者消费者模型就写完了!没错!就这么简单
我们来进行代码运行
- wait报错,下图加上try catch(不知不觉代码就多了起来,其实看到一大坨代码不要怕,就那几步有用)
public class Factory {
int count = 0;
public void put() {
synchronized (this) {
//判断是否满了
if(count == 10){
//在停止之前唤醒所有等待的线程
notifyAll();
//停止生产,进入等待状态
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
}
}
public void pull() {
synchronized (this) {
//判断是否满了
if(count == 10){
//在停止之前唤醒所有等待的线程
notifyAll();
//停止生产,进入等待状态
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
}
}
- 发现没有输出控制台看不到结果,加上输出,并用main方法运行起来
public class Factory {
int count = 0;
public void put() {
synchronized (this) {
//判断是否满了
if(count == 10){
System.out.println(Thread.currentThread().getName() + "---容量已满,阻塞");
//在停止之前唤醒所有等待的线程
notifyAll();
//停止生产,进入等待状态
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "---放入了第" + count);
}
}
public void pull() {
synchronized (this) {
//判断是否满了
if(count == 0){
System.out.println(Thread.currentThread().getName() + "容量已清空,阻塞");
//在停止之前唤醒所有等待的线程
notifyAll();
//停止生产,进入等待状态
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "拿走了第" + count);
count--;
}
public static void main(String[] args) {
Factory factory = new Factory();
new Thread(()->{
factory.put();
},"生产者1").start();
new Thread(()->{
factory.pull();
},"消费者1").start();
}
}
结束
请读者完全理解并写出以上代码以后在进行一下步
多生产者多消费者生产模式
再上面代码的基础上如果main方法这样写
public static void main(String[] args) {
Factory factory = new Factory();
new Thread(()->{
factory.put();
},"生产者1").start();
new Thread(()->{
factory.put();
},"生产者2").start();
new Thread(()->{
factory.pull();
},"消费者1").start();
new Thread(()->{
factory.pull();
},"消费者2").start();
}
这样写就有了两个生产者和两个消费者去操作数据,即多生产多消费,那么我们写的代码来执行会有什么问题呢?,
来看解析
假设此时仓库容量为2,
- 1号2号代表生产者,3号4号代表消费者
- 一开始count为0,3号先进入同步块执行,判断此时count为0,阻塞,注意此时阻塞在了这里,活着的线程为1,2,4
- 然后1号连续生产了2个,在生产第三个的时候同样阻塞,在阻塞前唤醒了其他所有线程,此时活着的线程为2,3,4
- 4号线程消费了2次后count为0,此时cpu刚好切换到了3号,然后3号从上图位置继续执行,不判断count是否为0,直接进行count–操作,此时count为-1!
出现以上问题我们只需要把if换成while
换成while后,3号线程被唤醒后会再去判断标志,为0,继续等待,这样就解决了多生产多消费的问题
多生产多消费优化
在解决了以上问题后,我们接下来看一种情况
3号消费线程一开始判断count为0,唤醒其他线程,自己阻塞,然后cpu切换到4号,4号判断count为0,唤醒其他线程,自己阻塞,然后cpu切换到3号。。。。。。这样循环好多次之后才切换到生产者,这样影响了效率
我们给出解决方案,利用lock锁的condition对象进行操作
他是怎么做到的?,lock锁是1.5版本后引入的,需要我们手动的加锁解锁,并提供了一个condition对象进行分类唤醒,也就是说,生产者方有一个condition1,消费者方有一个condition2,这样容器满了我们可以选择性的去唤醒对应方
附上代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Factory{
int count = 0 ;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
public void put(){
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
while (count == 10) {
System.out.println(Thread.currentThread().getName() + "---容量已满,阻塞");
condition2.signalAll();
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "---放入了第" + count);
lock.unlock();
}
}
public void pull(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true){
lock.lock();
while (count == 0) {
System.out.println(Thread.currentThread().getName() + "容量已清空,阻塞");
condition1.signalAll();
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "拿走了第" + count);
count--;
lock.unlock();
}
}
public static void main(String[] args) {
Factory factory = new Factory();
new Thread(()->{
factory.put();
},"生产者1").start();
new Thread(()->{
factory.put();
},"生产者2").start();
new Thread(()->{
factory.pull();
},"消费者1").start();
new Thread(()->{
factory.pull();
},"消费者2").start();
}
}
这也是我们最终的生产者消费者模式示例代码,关于Lock锁,右转百度2333