1. 程式人生 > >多執行緒--生產者消費者範例,使用lock和condition

多執行緒--生產者消費者範例,使用lock和condition

        上一篇的部落格已經介紹了生產者和消費者,最後還是遺留了一個問題,就是必須Notifyall才能保證喚醒對方執行緒,這樣降低了效率,那麼,有沒有什麼辦法可以指定我們來喚醒哪一個執行緒呢?

    在Jdk1.5以後將同步和鎖封裝成了物件,並將操作鎖的隱式方式定義到了該物件當中,將隱式方式定義到了該物件中,將隱式動作變成了顯示動作。

    這個物件就是lock:替代了同步程式碼快或者同步函式。將同步的隱式鎖操作變成現實鎖操作。同時更為靈活。可以一個鎖上加上多組監視器。

    

  主要方法:lock():獲取鎖   Unlock():釋放鎖,通常需要定義在finally程式碼塊中。 condition介面:出現替代了object中的wait,notify,notifyall方法。

    先看怎麼用Lock解決之前的問題。

public class ProducerandCustomer {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Resouce resouce=new Resouce();
			Producer producer=new Producer(resouce);
			Customer customer=new Customer(resouce);
			
			Thread t0=new Thread(producer);
			Thread t1=new Thread(producer);
			Thread t2=new Thread(customer);
			Thread t3=new Thread(customer);
			
			t0.start();
			t1.start();
			t2.start();
			t3.start();
	}

}

class Resouce{
	private String name;
	private int count=1;
	private boolean flag=false;
	//通過已有的鎖獲取兩組監視器,一組監視生產者,一組監視消費者
	Lock lock=new ReentrantLock();
	Condition producer_con=lock.newCondition();
	Condition consumer_con=lock.newCondition();
	
	public  void set(String name)
	{
		lock.lock();
		try {
			while(flag){
				try {
					producer_con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			this.name=name+count;
			count++;
			System.out.println(Thread.currentThread().getName()+"..生產者.."+this.name);
			flag=true;
			//notifyAll();
			consumer_con.signal();//喚醒消費者執行緒
		} finally {
			lock.unlock();//一定要釋放鎖
		}
		
		
	}
	
	public  void out(){
		lock.lock();
		try {
			while(!flag){
				try {
					consumer_con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"..消費者.."+this.name);
			flag=false;
			//notifyAll();
			producer_con.signal();
		}finally{
			lock.unlock();
		}
		
	}
}

class Producer implements Runnable{
	Resouce resouce;
	public Producer(Resouce resouce){
		this.resouce=resouce;
	}
	public void run(){
		while(true){
			resouce.set("烤鴨");
		}
	}
}

class Customer implements Runnable{
	Resouce resouce;
	public Customer(Resouce resouce) {
		// TODO Auto-generated constructor stub
		this.resouce=resouce;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			resouce.out();
		}
		
	}
	
}

再來看一組官方api給的程式碼,使用Lock

public class BounderBuffer {
	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;
	//putptr為生產者運算元組的角標,takeptr為消費者操作的角標,count用來計數,生產一個加1,消費一個減1.
	public void put(Object x) throws InterruptedException{
		lock.lock();
		try {
			while(count==items.length)//如果陣列已經滿,停止生產,凍結狀態
				notfull.await();
			items[putptr]=x;
			if(++putptr==items.length)//如果索引已經到達陣列長度。從0開始生產資料
				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();
		}
	}

lock和synchronized的區別:

一、synchronized和lock的用法區別

 
(1)synchronized(隱式鎖):在需要同步的物件中加入此控制,synchronized可以加在方法上,也可以加在特定程式碼塊中,括號中表示需要鎖的物件。
 
2)lock(顯示鎖):需要顯示指定起始位置和終止位置。一般使用ReentrantLock類做為鎖,多個執行緒中必須要使用一個ReentrantLock類做為對 象才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出。所以一般會在finally塊中寫unlock()以防死鎖。
 

二、synchronized和lock效能區別

 
synchronized是託管給JVM執行的,而lock是java寫的控制鎖的程式碼。在Java1.5中,synchronize是效能低效的。因為 這是一個重量級操作,需要呼叫操作介面,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用Java提供的Lock物件,效能更高一些。但 是到了Java1.6,發生了變化。synchronize在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致 在Java1.6上synchronize的效能並不比Lock差。

 

關於效能調優的問題,java程式設計思想的作者經過測試這樣寫到:很明顯,使用Lock通常會比synchronized要高效許多,而且synchronized的開銷看起來變化範圍太大,而Lock相對一致。這是否意味著你永遠都不應該使用synchronized關鍵字呢?這裡有兩個元素需要考慮:首先在測試程式碼中,互斥的方法體是非常小的。通常這是一個很好的習慣--只互斥那些必須互斥的部分。但是,在實際中,被互斥部分可能會比上面例項中的那些大許多,因此在這些方法體中花費的時間的百分比可能會明顯大於進入或退出互斥的開銷,這樣就泯滅了提高互斥速度帶來的所有好處。當然,唯一瞭解這一點的方式是--當你對效能調優時,應該立即--嘗試各種不同的方法觀察他們造成的影響。

而且和明顯,synchronized關鍵字產生的程式碼,於lock鎖需的“加鎖-try/finally-解鎖”方法所產生的程式碼相比,可讀性提高了很多。程式碼被閱讀的次數遠多於被編寫的次數。因此,以synchronized關鍵字入手,只有在效能調優的時候才替換為Lock物件這種做法,是具有實際意義的。

      

三、synchronized和lock機制區別


(1)synchronized原始採用的是CPU悲觀鎖機制,即執行緒獲得的是獨佔鎖
獨佔鎖意味著其 他執行緒只能依靠阻塞來等待執行緒釋放鎖。
(2)Lock用的是樂觀鎖方式
所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。樂觀鎖實現的機制就 是CAS操作(Compare and Swap)。