Java 多线程详解(四)------生产者和消费者

  经过前面三篇博客的介绍,基本上对Java的多线程有了必定的了解了,而后这篇博客根据生产者和消费者的模型来介绍Java多线程的一些其余知识。html

  咱们这里的生产者和消费者模型为:java

    生产者Producer 生产某个对象(共享资源),放在缓冲池中,而后消费者从缓冲池中取出这个对象。也就是生产者生产一个,消费者取出一个。这样进行循环。程序员

 

  第一步:咱们先建立共享资源的类 Person,它有两个方法,一个生产对象,一个消费对象多线程

public class Person {
	private String name;
	private int age;
	
	/**
	 * 生产数据
	 * @param name
	 * @param age
	 */
	public void push(String name,int age){
		this.name = name;
		this.age = age;
	}
	/**
	 * 取数据,消费数据
	 * @return
	 */
	public void pop(){
		System.out.println(this.name+"---"+this.age); 
	}
}

  第二步:建立生产者线程,并在 run() 方法中生产50个对象ide

public class Producer implements Runnable{
	//共享资源对象
	Person p = null;
	public Producer(Person p){
		this.p = p;
	}
	@Override
	public void run() {
		//生产对象
		for(int i = 0 ; i < 50 ; i++){
			//若是是偶数,那么生产对象 Tom--11;若是是奇数,则生产对象 Marry--21
			if(i%2==0){
				p.push("Tom", 11);
			}else{
				p.push("Marry", 21);
			}
		}
	}
}

  第三步:建立消费者线程,并在 run() 方法中消费50个对象this

public class Consumer implements Runnable{
	//共享资源对象
	Person p = null;
	public Consumer(Person p) {
		this.p = p;
	}
	
	@Override
	public void run() {
		for(int i = 0 ; i < 50 ; i++){
			//消费对象
			p.pop();
		}
	}
}

  因为咱们的模型是生产一个,立刻消费一个,那指望的结果即是 Tom---11,Marry--21,Tom---11,Mary---21......   连续这样交替出现50次线程

可是结果倒是:htm

Marry---21
Marry---21
Marry---21
Marry---21
Marry---21
......
Marry---21
Marry---21
Marry---21
Marry---21
Marry---21

 

为了让结果产生的更加明显,咱们在共享资源的 pop() 和 push() 方法中添加一段延时代码对象

/**
	 * 生产数据
	 * @param name
	 * @param age
	 */
	public void push(String name,int age){
		this.name = name;
		try {
			//这段延时代码的做用是可能只生产了 name,age为nul,消费者就拿去消费了
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.age = age;
	}
	/**
	 * 取数据,消费数据
	 * @return
	 */
	public void pop(){
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(this.name+"---"+this.age); 
	}  

  

这个时候,结果以下:blog

Marry---11
Tom---21
Marry---11
Tom---21
Marry---11
Tom---21
Marry---11
Tom---21
......
Tom---11
Tom---21
Marry---11
Tom---21
Marry---11
Marry---21

  

结果分析:这时候咱们发现结果全乱套了,Marry--21是固定的,Tom--11是固定的,可是上面的结果所有乱了,那这又是为何呢?并且有不少重复的数据连续出现,那这又是为何呢?

缘由1:出现错乱数据,是由于先生产出Tom--11,可是消费者没有消费,而后生产者继续生产出name为Marry,可是age尚未生产,而消费者这个时候拿去消费了,那么便出现 Marry--11。同理也会出现 Tom--21

缘由2:出现重复数据,是由于生产者生产一份数据了,消费者拿去消费了,可是第二次生产者生产数据了,可是消费者没有去消费;而第三次生产者继续生产数据,消费者才开始消费,这便会产生重复

解决办法1:生产者生产name和age必需要是一个总体一块儿完成,即同步。生产的中间不能让消费者来消费便可。便不会产生错乱的数据。如何同步能够参考:

        Java 多线程详解(三)------线程的同步:http://www.cnblogs.com/ysocean/p/6883729.html

     这里咱们选择同步方法(在方法前面加上 synchronized)

public class Person {
	private String name;
	private int age;
	
	/**
	 * 生产数据
	 * @param name
	 * @param age
	 */
	public synchronized void push(String name,int age){
		this.name = name;
		try {
			//这段延时代码的做用是可能只生产了 name,age为nul,消费者就拿去消费了
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.age = age;
	}
	/**
	 * 取数据,消费数据
	 * @return
	 */
	public synchronized void pop(){
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(this.name+"---"+this.age); 
	}
}

  结果以下:

Marry---21
Marry---21
Marry---21
Marry---21
Marry---21
Tom---11
Tom---11
......
Tom---11
Tom---11
Tom---11
Tom---11
Tom---11

问题:仍是没有解决上面的问题2,出现重复的问题。指望的结果是 Tom---11,Marry--21,Tom---11,Mary---21......   连续这样交替出现50次。那如何解决呢?

解决办法:生产者生产一次数据了,就暂停生产者线程,等待消费者消费;消费者消费完了,消费者线程暂停,等待生产者生产数据,这样来进行。

 

 

这里咱们介绍一个同步锁池的概念:

  同步锁池:同步锁必须选择多个线程共同的资源对象,而一个线程得到锁的时候,别的线程都在同步锁池等待获取锁;当那个线程释放同步锁了,其余线程便开始由CPU调度分配锁

 

关于让线程等待和唤醒线程的方法,以下:(这是 Object 类中的方法)

  

 

  

 

wait():执行该方法的线程对象,释放同步锁,JVM会把该线程放到等待池中,等待其余线程唤醒该线程

notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待(注意锁池和等待池的区别)

notifyAll():执行该方法的线程唤醒在等待池中等待的全部线程,把线程转到锁池中等待。

注意:上述方法只能被同步监听锁对象来调用,这也是为啥wait() 和 notify()方法都在 Object 对象中,由于同步监听锁能够是任意对象,只不过必须是须要同步线程的共同对象便可,不然别的对象调用会报错:        java.lang.IllegalMonitorStateException

 

假设 A 线程和 B 线程同时操做一个 X 对象,A,B 线程能够经过 X 对象的 wait() 和 notify() 方法来进行通讯,流程以下:

①、当线程 A 执行 X 对象的同步方法时,A 线程持有 X 对象的 锁,B线程在 X 对象的锁池中等待

②、A线程在同步方法中执行 X.wait() 方法时,A线程释放 X 对象的锁,进入 X 对象的等待池中

③、在 X 对象的锁池中等待锁的 B 线程得到 X 对象的锁,执行 X 的另外一个同步方法

④、B 线程在同步方法中执行 X.notify() 方法,JVM 把 A 线程从等待池中移动到 X 对象的锁池中,等待获取锁

⑤、B 线程执行完同步方法,释放锁,等待获取锁的 A 线程得到锁,继续执行同步方法

 

那么为了解决上面重复的问题,修改代码以下:

public class Person {
	private String name;
	private int age;
	
	//表示共享资源对象是否为空,若是为 true,表示须要生产,若是为 false,则有数据了,不要生产
	private boolean isEmpty = true;
	/**
	 * 生产数据
	 * @param name
	 * @param age
	 */
	public synchronized void push(String name,int age){
		try {
			//不能用 if,由于可能有多个线程
			while(!isEmpty){//进入到while语句内,说明 isEmpty==false,那么表示有数据了,不能生产,必需要等待消费者消费
				this.wait();//致使当前线程等待,进入等待池中,只能被其余线程唤醒
			}
			
			//-------生产数据开始-------
			this.name = name;
			//延时代码
			Thread.sleep(10);
			this.age = age;
			//-------生产数据结束-------
			isEmpty = false;//设置 isEmpty 为 false,表示已经有数据了
			this.notifyAll();//生产完毕,唤醒全部消费者
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	/**
	 * 取数据,消费数据
	 * @return
	 */
	public synchronized void pop(){
		try {
			//不能用 if,由于可能有多个线程
			while(isEmpty){//进入 while 代码块,表示 isEmpty==true,表示为空,等待生产者生产数据,消费者要进入等待池中
				this.wait();//消费者线程等待
			}
			//-------消费开始-------
			Thread.sleep(10);
			System.out.println(this.name+"---"+this.age); 
			//-------消费结束------
			isEmpty = true;//设置 isEmpty为true,表示须要生产者生产对象
			this.notifyAll();//消费完毕,唤醒全部生产者
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

  

结果:

Tom---11
Marry---21
Tom---11
Marry---21
Tom---11
Marry---21
Tom---11
......
Marry---21
Tom---11
Marry---21
Tom---11
Marry---21
Tom---11
Marry---21  

 

那么这即是咱们期待的结果,交替出现。

 

 

死锁:

①、多线程通讯的时候,很容易形成死锁,死锁没法解决,只能避免

②、当 A 线程等待由 B 线程持有的锁,而 B 线程正在等待由 A 线程持有的锁时发生死锁现象(好比A拿着铅笔,B拿着圆珠笔,A说你先给我圆珠笔,我就把铅笔给你,而B说你先给我铅笔,我就把圆珠笔给你,这就形成了死锁,A和B永远不能进行交换)

③、JVM 既不检测也不避免这种现象,因此程序员必须保证不能出现这样的状况

 

Thread 类中容易形成死锁的方法(这两个方法都已通过时了,不建议使用):

suspend():使正在运行的线程放弃 CPU,暂停运行(不释放锁)

resume():使暂停的线程恢复运行

情景:A 线程得到对象锁,正在执行一个同步方法,若是 B线程调用 A 线程的 suspend() 方法,此时A 暂停运行,放弃 CPU 资源,可是不放弃同步锁,那么B也不能得到锁,A又暂停,那么便形成死锁。

 

解决死锁法则:当多个线程须要访问 共同的资源A,B,C时,必须保证每个线程按照必定的顺序去访问,好比都先访问A,而后B,最后C。就像咱们这里的生产者---消费者模型,制定了必须生产者先生产一个对象,而后消费者去消费,消费完毕,生产者才能在开始生产,而后消费者在消费。这样的顺序便不会形成死锁。

相关文章
相关标签/搜索