Java多线程——消费者与生产者的关系

本文介绍了Java中多线程处理的概念,通过消费者与生产者的关系阐述了线程同步的重要性和实现方式。文章通过馒头和篮子的比喻,详细解释了如何使用栈的数据结构和synchronized关键字来确保线程安全。生产者和消费者线程通过wait()和notify()方法协调工作,避免死锁,并展示了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程:CPU中各种任务在交替执行过程中,被称为多线程处理。其中,每个任务的一次动态执行过程被称为进程。进程执行过程中的暂停被称为中断。进程通过中断被分解成若干段,每一段被称为一个线程。

在消费者与生产者中,我们设定单个生产者的每次活动是馒头 +1;单个消费者每次进行的是馒头 -1;然后消费者和生产者的活动时间不确定,同时生产者和消费者的数量不确定,该经典问题我们使用多线程实现:

我们首先确定实物,即生产者的每次生成的物品,在这里我们使用馒头代替。在WoTou的类中,我们可以看到有着自己独特的属性,即馒头的  id,我们可以在控制台清晰的看到馒头的生产和消费过程,具体实现代码如下:

class WoTou{	//馒头,生产者生产的馒头
	int id;
	WoTou(int id){
            this.id = id;
            }
	public String toString(){
	    return "WoTou:"+ id;
	    }
}

我们需要确定馒头的存储容器,即在消费和生产发生的时候,我们需要在什么地方进行判断,馒头是否耗尽,或者馒头是否满溢。每次活动都是相当于在栈中取东西和放东西,所以我们根据栈的特性为:先进后出 原则。

class SyncStack {//篮子容器,放馒头用的
	int index = 0;
	WoTou[] arrWT = new WoTou[6];
		
}

在篮子容器中我们简单定义栈的最大容量为6,我们之后设定生产者的类,定义为producer类。我们知道producer有着一些自己的独特属性,即往篮子SyncStack中放WoTou,每次进行馒头+1操作,还有生产者的操作Time间隔,是隔10毫秒还是5毫秒,我们使得producer类实现其Runnerable接口,调用线程的run()方法,具体代码如下:

class Producer implements Runnable{		//生产者
	SyncStack ss= null;
	Producer(SyncStack ss){
		this.ss = ss;
	}
		
	public void run(){
		for(int i=0;i<20;i++){
		WoTou wt = new WoTou(i);
		ss.push(wt);
		System.out.println("生产者:"+wt);
		try {
			Thread.sleep((int)(Math.random()*1000));
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		    }
		}
	    }
}

生产者在往栈中添加数据的时候,我们使用push方法进行插入。同时定义插入的馒头数量,这里的数量并不是篮子的馒头数量。篮子里边虽然只能一次性装6个馒头,但是消费者是同时间进行操作的,所以篮子的馒头数量在消费之后,生产者继续进行生产,一直到生产20个馒头为止。

消费者我们这里定义为Consumer类,消费者和生产者有一定的相似性,但是我们需要消费在篮子中拿的过程,我们使用pop操作,同时定义消费数量,以及操作间隔,这里都是使用线程的sleep(时间)方法, 代码如下:

class Consumer implements Runnable{		//消费者
	SyncStack ss= null;
	Consumer(SyncStack ss){
		this.ss = ss;
	}
		
	public void run(){
		for(int i=0;i<20;i++){
		WoTou wt = ss.pop();
		System.out.println("消费量:"+wt);
		try {
			Thread.sleep((int)(Math.random()*1000));
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		    }
	}
}

最后我们考虑在篮子中,假如容量已满,即生产者生产过多,消费的太慢,那么生产者需要休息;同样的消费过快,容器数量为0,消费者开始休息。我们在两种情况发生的时候,需要设置两种方法,在极限情况发生的时候,进行判断。简单实现代码如下:

public synchronized void push(WoTou wt){	//往篮子中放馒头
		while(index == arrWT.length){
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
		this.notify();
		arrWT[index] = wt;
		index++;
}

我们需要确定是我们在生产者进行活动的时候,不能受到其他线程的影响,即我们需要保持在某一时刻只有一个线程进行操作,这种情况我们需要使用线程的一个synchronized方法。这个方法称为死锁,锁定当前方法体执行的过程中,保持其独立性。即在方法体的前面使用synchronized关键字修饰

从篮子中取出馒头的操作类似于生产者,实现代码如下:

public synchronized WoTou pop(){	//从篮子中取馒头
	while(index == 0){
		try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		    }
		this.notify();
		index--;
		return arrWT[index];
}

我们会发现在上面两种动作中,考虑到一个问题,即数量到极限之后,我们的动作停止了。生产者在篮子容量到达6时,动作停止;消费者在篮子数量到0后,停止行为。我们这时候开始使用线程的notify方法,唤醒休眠的线程,因为线程使用了wait方法,我们可以唤醒之后,在进行各自的行为。因此篮子SynStack类中详细代码为:

class SyncStack {//篮子容器,放馒头用的
		int index = 0;
		WoTou[] arrWT = new WoTou[6];
		
	public synchronized void push(WoTou wt){	//往篮子中放馒头
		while(index == arrWT.length){
			try {
			    this.wait();                //线程等待
			} catch (InterruptedException e) {
			        // TODO: handle exception
				e.printStackTrace();
			    }
			}
			this.notify();            //唤醒wait的线程
			arrWT[index] = wt;
			index++;
		}
		
	public synchronized WoTou pop(){	//从篮子中取馒头
		while(index == 0){
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO: handle exception
				e.printStackTrace();
			}
			    }
			this.notify();
			index--;
			return arrWT[index];
	}
}

我们可以修改生产者和消费者的数量和操作间隔,不一定生产+1同时消费-1。我们可以根据修改进行更改代码,上面的运行在控制台的数据为:

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值