挑灯夜读——Java并发:生产者与消费者问题

模型是什么?

  • 在学习并发编程的思想时,我们不能一上来就是条条框框书写的并发知识,相反,我们需要引进来这样的问题,供大家思考。

生产者与消费者,对产品必须保持正确的逻辑。
例如:产品为0,无法消费,只能生产
再如:仓库已满,无法生产,只能消费

  • 上面的问题就是我们所说的关于生产者和消费者之间的关系,要保持这样的关系,必须确保操作的安全性,也就是我们的代码块需要加入锁这种概念。
  • 该模式是一种数据操作的思想,为后面的编程打下坚实的基础。

用什么锁?

synchronized锁
  • 但凡提到锁这个概念,脑子里不止一遍地蹦出synchronized关键字,当然这样的锁是JVM对其进行操作,我们无需关系何时加锁,何时解锁。只需要将锁的位置选好即可。
  • 所以这里先使用synchronized进行模型展示:
public class Main{
	public static void main(String[] args){
		Data data = new Data();
		new Thread(()->{
			data.increment();
		}).start();
		new Thread(()->{
			data.decrement();
		}).start();
	}
}
//数据操作类,减少使用继承Thread或者实现Runnable等方式
class Data{
	//num作为标志,以此来解决
	private int num = 0;
	//加1操作
	public synchronized void increment(){
		while(num!=0){
			this.wait();
		}
		num = 1;
		System.out.println(Thread.currentThread().getName());
		this.notify();
	}
	//减1操作
	public synchronized void decrement(){
		while(num==0){
			this.wait();
		}
		num = 0;
		System.out.println(Thread.currentThread().getName());
		this.notify();
	}
}
  • 以上为使用synchronized进行加锁的过程。同样的我们可以使用Lock进行加锁
Lock锁
  • 使用Lock锁同样能够对生产者与消费者这样的模型进行设计,只需要将相关进行改变即可。
    在这里插入图片描述
  • 如上图所示:
  • 我们只是将阻塞和唤醒的方式改变了一下,其它并没有改变。
public class Main{
	public static void main(String[] args){
		Data data = new Data();
		new Thread(()->{
			data.increment();
		}).start();
		new Thread(()->{
			data.decrement();
		}).start();
	}
}
//数据操作类,减少使用继承Thread或者实现Runnable等方式
class Data{
	//num作为标志,以此来解决
	private int num = 0;
	//加1操作
	private Lock lock = new ReenTrantLock();
	private Condition condition = lock.newCondition();
	public void increment(){
		lock.lock();
		try{
			while(num!=0){
			condition.await();
			}
			num = 1;
			System.out.println(Thread.currentThread().getName());
			condition.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	//减1操作
	public synchronized void decrement(){
		lock.lock();
		try{
			while(num==0){
			condition.await();
			}
			num = 0;
			System.out.println(Thread.currentThread().getName());
			condition.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}
  • 这样我们就使用Lock对代码块进行了加锁。原理就不过多讲解。

那么问题来了

  • 假如我们的生产者和消费者并非只有一个,也就是有多个消费者,多个生产者。这个时候,我们需要生产与消费的步骤可控,也就是指定生产,指定消费。
  • 这个时候我们可以使用Lock锁的condition来指定线程,并对其进行阻塞或者唤醒,也就是有指定目标的唤醒。
public class Main{
	public static void main(String[] args){
		Data data = new Data();
		new Thread(()->{
			data.increment();
		}).start();
		new Thread(()->{
			data.decrement();
		}).start();
	}
}
//数据操作类,减少使用继承Thread或者实现Runnable等方式
class Data{
	//num作为标志,以此来解决
	private int num = 0;
	//加1操作
	private Lock lock = new ReenTrantLock();
	private Condition condition1 = lock.newCondition();
	private Condition condition2 = lock.newCondition();
	private Condition condition3 = lock.newCondition();
	private Condition condition4 = lock.newCondition();
	//第一个生产者
	public void increment1(){
		lock.lock();
		try{
			while(num!=0){
			condition1.await();
			}
			num = 1;
			System.out.println(Thread.currentThread().getName());
			condition2.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	//第二个生产者
	public void increment2(){
		lock.lock();
		try{
			while(num!=0){
			condition3.await();
			}
			num = 1;
			System.out.println(Thread.currentThread().getName());
			condition4.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	//第一个消费者
	public synchronized void decrement1(){
		lock.lock();
		try{
			while(num==0){
			condition2.await();
			}
			num = 0;
			System.out.println(Thread.currentThread().getName());
			condition3.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	//第二个消费者
	public synchronized void decrement2(){
		lock.lock();
		try{
			while(num==0){
			condition4.await();
			}
			num = 0;
			System.out.println(Thread.currentThread().getName());
			condition1.signal();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}
  • 上面我们创建了四个condition,分别控制两个生产者模型和两个消费者模型。
  • 运行的逻辑为:
  • 1、先使用condition1锁住生产者1代码块,结束后唤醒线程2,也就是唤醒消费者1模型
  • 2、唤醒消费者1后,condition2锁住消费者1代码块,结束后唤醒线程3,也就是生产者2
  • 3、唤醒生产者2后,condition3锁住生产者2代码块,结束后唤醒线程4,也就是消费者2
  • 4、消费者2被唤醒后,condition4锁住消费者2代码块,结束后唤醒线程1,也就是生产者1.
  • 上面的唤醒一圈后,又回到了原点。

其它锁辅助

  • 提到锁辅助,很多人可能都觉得很疑惑,锁辅助器有很多,下面主要讲解以下三个并发包下的类:
CountDownLatch
  • 一般称其为“计数器”,也就是我们常说的倒计时器,主要作用就是对我们的线程运行计时。多说无益,直接上代码:
public class Main{
	public static void main(String[] args) throws InterruptedException{
		CountDownLatch count = new CountDownLatch(6);
		for(int i=1;i<=6;i++){
			new Thread(()->{
				count.countDown();
				System.out.println(Thread.currentThread().getName());
			},String.valueOf(i)).start();
		}
		count.await();
		System.out.println("模型结束");		
	}
}
CyclicBarrier
  • 既然有减一的操作,必定有加一的操作,CyclicBarrier作为一个类,它的作用为:

开发者文档:允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

  • 它的作用就是一道屏障,为我们的数据安全作出极大的贡献。具体代码就不不过多展示。
Semaphore
  • 一个技术信息量,通过acquire阻塞线程,获取信息,然后通过release释放锁,等操作。
  • 它的主要作用是限制线程的数量,控制内存运行等安全。

结语

  • 本次学习还需加深理解,具体的实现也已经弄明白,希望再接再厉。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米店不卖你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值