JAVA学习第二十六课(多线程(六))- 多生产者多消费者问题

多生产者多消费者问题

以生产馒头 消费馒头为例。

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;

	public synchronized void set(String name)
	{
		if (flag)
		{
				try {
				this.wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		this.name = name + count;
		count++;
			System.out.println(Thread.currentThread().getName()+"---生产者----"+this.name);
			flag = true;
			notify();
	}
	public synchronized void out()
	{
		if (!flag) {
			try {
				this.wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		System.out.println(Thread.currentThread().getName()+"---------消费者--------"+this.name);
		flag = false;
		notify();
	}
}

class Producer implements Runnable
{
	private Resource r;
	Producer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.set("馒头");
		}
	}
}
class Consumer implements Runnable
{
	private Resource r;
	Consumer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}

public class Main
{
	public static void main(String[] args)
	{
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}

上述代码容易出现一个问题,就是同一个馒头被多次消费,或者有的馒头没有被消费。单生者,单消费者不会出现这个问题

原因:notify 唤醒的线程是任意一个,比如t0 t1都是等待状态,t0活了之后,t1也可能被唤醒,所以就会产生多个馒头而没有被消费的情况,针对这个问题,flag的判断可以改为循环,这样却容易造成了死锁情况:t0 t1被wait了,t2正常消费一次,flag = false,唤醒了t0,因为循环t2 t3被wait了,t0执行一次,flag = true,假设唤醒t1,判断t0进行wait,while,t1也进入了等待,死锁状态

PS:冻结状态的线程被唤醒后本次就不参与判断,向下执行,所以简单来说,多个馒头没有被消费问题的产生,是因为冻结状态的线程不再继续参与判断造成的,而死锁是因为循环判断造成的

多生产多消费问题解决:

notify,改为notifyAll,当前本类线程中的一个正常执行后,唤醒所有线程,当前同类线程因为while,自然会继续等待,对方任意一个线程执行一次,对方剩余线程继续等待,这样就实现了生成对应消费

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;

	public synchronized void set(String name)
	{
		while (flag)
		{
				try {
				this.wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		this.name = name + count;
		count++;
			System.out.println(Thread.currentThread().getName()+"---生产者----"+this.name);
			flag = true;
			notifyAll();
	}
	public synchronized void out()
	{
		while (!flag) {
			try {
				this.wait();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		System.out.println(Thread.currentThread().getName()+"---------消费者--------"+this.name);
		flag = false;
		notifyAll();
	}
}

总结:

if判断,只会判断一次,容易造成不该唤醒的线程被唤醒了,出现问题

while判断,虽然解决了线程获取执行权后,是否要运行

notify,只能唤醒任意一个线程,但是唤醒本方线程,没有意义,且while+notify = 死锁

notifyAll,解决了本方线程一定会唤醒对方线程

所以,多生产多消费问题 = while + notifyAll,但是也造成了效率低的问题(本方不该唤醒,也被唤醒了)

新特性(JDK1.5升级)

如何不唤醒本方,只唤醒对方呢

void Fu()//前期
{
	synchronized (obj) //关于锁的操作是隐式的,只有锁自己知道
	{
		code....
	}
}
//后期升级,将锁这一事物封装成了对象 Lock L = new ReentrantLock();,将操作锁的隐式方式定义到了对象中
void Fu()
{
	L.lock();//获取锁
	code..
	L.unlock();//释放锁
}

接口Lock

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

ConditionObject 监视器方法(waitnotify
notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了
synchronized
方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

Lock l = ...;
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }

Lock接口:替代了同步代码块或同步函数,将同步锁的隐式操作变成现实操作,更为灵活,可以一个锁加多个监视器

方法:

L.lock();//获取锁

L.unlock();//释放锁,一般与finally代码块连用

Condition接口:实现了Object中wait(),notify(),notifyAll()方法,将这些监视器方法进行了单独的封装,变成Condition监视器对象,可以任意锁进行组合

await()  相当于 wait

signal() 相当于 notify

signalAll(); 相当于 notifyAll

import java.util.concurrent.locks.*;

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;
	//创建一个锁
	Lock L = new ReentrantLock();
	//通过已有的锁,获取该锁监视器对象(Condition)
	//Condition con = L.newCondition();
	//通过已有的锁,获取两组监视器,一个监视生产者,一个监视消费者
	Condition pro_con = L.newCondition();
	Condition consu_con = L.newCondition();

	public  void set(String name)
	{
		L.lock();
		try {
					while (flag)
					{
						try {
							pro_con.await();//线程冻结
						}catch (Exception e) {
							// TODO: handle exception
						}
					}
					this.name = name + count;
					count++;
				System.out.println(Thread.currentThread().getName()+"---生产者5.0----"+this.name);
					flag = true;
					//con.signalAll();//唤醒所有线程
					consu_con.signal();//唤醒消费者线程,不用All
		} catch (Exception e) {
			// TODO: handle exception
		}
		finally
		{
			L.unlock();//释放锁
		}

	}
	public void out()
	{
		L.lock();
		try {
						while (!flag) {
					try {
						consu_con.await();
					} catch (Exception e) {
						// TODO: handle exception
					}
				}
		System.out.println(Thread.currentThread().getName()+"---------消费者5.0--------"+this.name);
				flag = false;
				//con.signalAll();
				pro_con.signal();
		} catch (Exception e) {
			// TODO: handle exception
		}
		finally
		{
			L.unlock();
		}

	}
}

class Producer implements Runnable
{
	private Resource r;
	Producer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.set("馒头");
		}
	}
}
class Consumer implements Runnable
{
	private Resource r;
	Consumer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}

public class Main
{
	public static void main(String[] args)
	{
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}

实际开发是这样的代码

class BoundedBuffer {//缓冲区
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition();
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];//创建一个大的容器
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }
时间: 2024-10-12 12:50:27

JAVA学习第二十六课(多线程(六))- 多生产者多消费者问题的相关文章

JAVA学习第二十四课(多线程(三))- 线程的同步

继续以卖票为例 一.线程安全问题的解决 同步的第一种表现形式:同步代码块 思路: 将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不允许参与运算的,必须要当期线程把代码执行完毕后,其他线程才可以参与运算 在java中用同步代码块解决这个问题 同步代码块格式: synchronized(对象) { 需要被同步的代码部分 } class Ticket implements Runnable { private int num = 100; Object god = ne

JAVA学习第二十二课(多线程(二))- (多线程的创建方式一 :继承Thread类)

线程是程序中的执行线程.Java 虚拟机允许应用程序并发地运行多个执行线程. 创建新执行线程有两种方法. 一种方法是将类声明为 Thread 的子类.该子类应重写Thread 类的run 方法.另一种方法是声明实现 Runnable 接口的类.该类然后实现run 方法. 创建线程方式一:继承Thread类 一.创建线程的步骤: 1.定义一个类继承Thread类 2.覆盖Thread中的run()方法 3.直接创建Thread类子类的对象 4.调用start方法开启线程,并调用线程的任务run方法

JAVA学习第二十八课(多线程(七))- 停止线程和多线程面试题

重点掌握 /* * wait 和 sleep 区别? * 1.wait可以指定时间也可以不指定 * sleep必须指定时间 * 2.在同步中,对CPU的执行权和锁的处理不同 * wait释放执行权,释放锁    sleep释放执行权,不释放锁 */ //同步里具备执行资格的线程不止一个,但是能得到锁的只有一个,所以能执行的也只有一个 一.停止线程的方式 不可能让线程一直在运行,所以需要让线程停止 1.定义循环结束标记 一般而言,线程运行代码都是循环的,只要控制了循环就可以结束任务 2.使用int

JAVA学习第二十五课(多线程(四))- 单例设计模式涉及的多线程问题

一.多线程下的单例设计模式 利用双重判断的形式解决懒汉式的安全问题和效率问题 //饿汉式 /*class Single { private static final Single t = new Single(); private Single(){} public static Single getInstance() { return t; } } */ //懒汉式 class Single { private static Single t = null; private Single()

JAVA学习第二十八课(常用对象API)- String类

多线程告一段落,开始常用对象API的涉及,背也要背下来!!! 日后开发,遇见最多的对象是文字,也就是字符串 String类 字符串是一个特殊对象 字符串一旦初始化就不可以被改变 一.特点 public class Main { public static void main(String[] args) { Demo1(); System.out.println("--------------"); Demo2(); } /*演示字符串的第一种定义方式,并明确字符串常量池的特点*/ p

JAVA学习第二十九课(经常使用对象API)- String类

多线程告一段落,開始经常使用对象API的涉及,背也要背下来!.! 日后开发,遇见最多的对象是文字,也就是字符串 String类 字符串是一个特殊对象 字符串一旦初始化就不能够被改变 一.特点 public class Main { public static void main(String[] args) { Demo1(); System.out.println("--------------"); Demo2(); } /*演示字符串的第一种定义方式,并明白字符串常量池的特点*/

Java学习第二十四天

1:多线程(理解) (1)JDK5以后的针对线程的锁定操作和释放操作 Lock锁 (2)死锁问题的描述和代码体现 (3)生产者和消费者多线程体现(线程间通信问题) 以学生作为资源来实现的 资源类:Student 设置数据类:SetThread(生产者) 获取数据类:GetThread(消费者) 测试类:StudentDemo 代码: A:最基本的版本,只有一个数据. B:改进版本,给出了不同的数据,并加入了同步机制 C:等待唤醒机制改进该程序,让数据能够实现依次的出现 wait() notify

JAVA学习第二十六课(多线程(五))- 多线程间的通信问题

一.线程间的通信 实例代码: 需求是:输入一个姓名和性别后,就输出一个姓名和性别 class Resource { String name; String sex ; } class Input implements Runnable { Resource r; Input(Resource r) { this.r = r; } public void run() { int x = 0; while(true) { synchronized (r) { if(x==0) { r.name =

JAVA学习第二十课(异常的应用和注意事项(三))

十.异常的应用 import java.util.Random; import javax.rmi.CORBA.Stub; /* *老师用电脑上课 *用面向对象思考问题 *问题设计两个对象 *老师,电脑 * *可能发生的问题: *电脑蓝屏.电脑冒烟 */ class Lan extends Exception//蓝屏 { Lan(String problem) { super(problem); } } class Fir extends Exception//冒烟 { Fir(String p