天天看點

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

一.線程間通信

1.生産者與消費者問題

//多線程經典,賣烤鴨程式--生産者與消費者
//定義一個資源類Resource
class Resource{
	private String name;
	private int count = 1;
	//set()方法給生産者調用
	public void set(String name){
		this.name = name + count;
		//調用set()方法count自增
		count++;
		System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
		
	}
	//out()方法給消費者調用
	public void out(){
		System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
		
	}
}

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();
		}
	}
}

class  ProducerConsumerDemo{
	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(con);
		t0.start();
		t1.start();

	}
}


           

運作結果:

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

問題:消費者消費了生産者沒有生産的資源

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

分析:set( )方法内操作了共享資料count,而且有多條操作語句,出現了多線程安全問題,消費者的線程可能搶在生産者前列印資料(如圖烤鴨1638)

解決:使用同步函數解決多線程安全問題

隻需更改Resource類,将set( )函數和out( )函數加上synchronized關鍵字

class Resource{
	private String name;
	private int count = 1;
	
	public synchronized voidset(String name){
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
		
	}
	
	public void synchronized out(){
		System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
		
	}
}
           

運作結果:

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

問題:消費者隻能消費最近一次生産的資源

原因:兩個方法使用了同一個鎖,同一時間隻能有一個方法執行,而set( )方法執行時count不斷自增,out( )方法執行時count值則一直不變

顯然僅僅加一個鎖是不能達到我們的預期的,這時就要考慮其他解決辦法。即線程間通信

2.線程間通信的概念

多個線程在操作同一個資源,但是操作的動作卻不一樣

實作方式:

1:将資源封裝成對象

2:将線程執行的任務(任務其實就是run方法。)也封裝成對象

3.等待喚醒機制:

涉及到的方法:

wait( )

将同步中的線程處于當機狀态,釋放了執行權和執行資格,同時将線程對象存儲到線程池中

notify( )

喚醒線程池中某一個等待線程

notifyAll( )

喚醒線程池中的所有線程

使用前提:

1,這些方法都需要定義在同步中,不使用同步則這些方法沒有意義

2,這些方法必須要标示所屬的鎖。

A鎖上的線程被wait了,那這個線程就會被當機然後儲存到A鎖的線程池中,隻能A鎖的notify喚醒

這三個方法都定義在Object類中,因為這三個方法在使用過程中必須要辨別所屬的鎖對象,而鎖對象可以是任意對象,是以這些可以被任意對象調用的方法一定定義在Object類中

//使用等待喚醒機制解決生産者和消費者問題
class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name){
		//如果flag=true,則生産者進入等待狀态
		while(flag)
			try{this.wait();}catch(InterruptedException e){}
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
		flag = true;
		//執行完後喚醒消費者
		this.notify();
		
	}
	public synchronized void out(){
		//如果flag=false,則消費者進入等待狀态
		while(!flag)
			try{this.wait();}catch(InterruptedException e){}
		System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
		flag = false;
		//執行完後喚醒生産者
		this.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();
		}
	}
}

class  ProducerConsumerDemo{
	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(con);
		t0.start();
		t1.start();

	}
}

           

執行結果

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

wait和sleep差別:

1.喚醒方式不同

wait:可以指定時間也可以不指定時間,不指定時間則隻能由對應的notify或者notifyAll來喚醒。

sleep:必須指定時間,時間到自動從當機狀态轉成運作狀态(臨時阻塞狀态)。

2.線程持有的鎖的釋放

wait:線程會釋放執行權,而且線程會釋放鎖。

Sleep:線程會釋放執行權,但是不釋放鎖。

4.多生産多消費

public class ProducerConsumerDemo {


    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();

    }
}
           

定義多個生産者和消費者

運作結果:

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

現象:程式停止不動,不在繼續向下執行,但是卻不是死鎖,因為程式中沒有同步的嵌套

分析:set( )和get( )方法中喚醒線程使用的是notify( )方法,隻能喚醒一個線程。是以可能出現一個生産者線程喚醒了另一個生産者線程,而flag值是true,被喚醒的這個生産者線程經過while判斷後也進入了wait等待狀态,然後程式中就沒有可以執行的線程了,程式停止。

解決:使用notifyAll( )方法,将所有線程都喚醒

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(InterruptedException e){}
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生産者..."+this.name);
		flag = true;
		//隻需将notify更改為notifyAll
		this.notifyAll();
		
	}
	public synchronized void out(){
		while(!flag)
			try{this.wait();}catch(InterruptedException e){}
		System.out.println(Thread.currentThread().getName()+"...消費者........"+this.name);
		flag = false;
		this.notifyAll();
	}
}
           
多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

二.線程的停止

1.使用stop方法(已過時)

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

2.結束run( )方法

run方法裡一般會定義循環,是以隻要結束循環即可。

第一種方式:定義循環的結束标記。

第二種方式:如果線程處于了當機狀态,是不可能讀到标記的,這時就需要通過Thread類中的interrupt方法,将其當機狀态強制清除。讓線程恢複具備執行資格的狀态,讓線程可以讀到标記,并結束。

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

三.Lock接口和Condition接口

解決線程安全問題使用同步的形式(同步代碼塊和同步函數),其實最終使用的都是鎖機制。線程進入同步就是具備了鎖,執行完畢離開同步,就是釋放了鎖。擷取鎖,釋放鎖的動作是鎖對象的内容。在面向對象思想的指導下,将鎖單獨定義為對象并封裝對鎖的操作。Java的設計者專門開發了一個Lock接口,将擷取鎖,釋放鎖等操作都定義到了這個接口中

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

同步是隐示的鎖操作,而Lock接口是顯示的鎖操作,Lock接口的出現就替代了同步。

在之前的版本中使用Object類中wait、notify、notifyAll的方式來完成的。那是因為同步中的鎖是任意對象,是以操作鎖的等待喚醒的方法都定義在Object類中。

而現在鎖是指定對象Lock。是以查找等待喚醒機制方式需要通過Lock接口來完成。而Lock接口中并沒有直接操作等待喚醒的方法,而是将這些方式又單獨封裝到了Condition接口中。

Condition接口将Object中的三個方法進行單獨的封裝。并提供了功能一緻的方法 await( )、signal( )、signalAll( )

多線程(三)--多線程間通信一.線程間通信二.線程的停止三.Lock接口和Condition接口

使用Lock接口實作多生産多消費

import java.util.concurrent.locks.*;

class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	//建立一個Lock接口的實作類ReentrantLock的對象
	Lock lock = new ReentrantLock();
	//通過lock鎖建立兩個監聽器,分别監聽生産者和消費者
	Condition producer_con = lock.newCondition();
	Condition consumer_con = lock.newCondition();

	public void set(String name){
		//擷取鎖(相當于加同步)
		lock.lock();
		try{
			while(flag)
			//監聽器調用await()方法使線程進入等待狀态
			try{producer_con.await();}catch(InterruptedException e){}
			this.name = name + count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...生産者5.0..."+this.name);
			flag = true;
			//喚醒消費者的線程
			consumer_con.signal();
		}
		finally{
			//操作執行完畢,釋放鎖
			lock.unlock();
		}
		
	}

	public void out(){
		lock.lock();
		try{
			while(!flag)
			try{cousumer_con.await();}catch(InterruptedException e){}	
			System.out.println(Thread.currentThread().getName()+"...消費者.5.0......."+this.name);
			flag = false;
			producer_con.signal();
		}
		finally{
			lock.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();
		}
	}
}



class  ProducerConsumerDemo2{
	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();

	}
}


           

繼續閱讀