Java多線程與併發應用-(9)-鎖lock+條件阻塞conditon實現線程同步通信

一. lock可以代替synchronized關鍵字實現互斥功能。使用方法如下:

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

需要注意的是。

1.需要互斥的一個或多個方法要使用同一個互斥鎖。

2.在被鎖包含的代碼塊中,要使用finally塊將鎖釋放。

二. Condition的await方法(注意不是wait方法)可以替換傳統通信中的wait方法,對應的signal方法替換notify。

在傳統通信的條件判斷時,我們會用while而不是if做條件判斷,是爲了虛假喚醒。那麼什麼是虛假喚醒呢?

虛假喚醒即:如:我們要求AB方法按順序執行,有5個線程執行A,5個線程執行B,如果某時刻全部A等待,當其中A1被喚醒,並執行完代碼後,會調用notify方法,其本意是喚醒B模塊執行線程,但是由於AB公用一個鎖,所以可能將A喚醒,即喚醒了不該執行的代碼,這就是虛假喚醒。

所以我們使用while條件,即使被喚醒了,我們還會做一次條件判讀,這樣被虛假喚醒的代碼將再一次等待。

這就要求程序員來控制避免虛假喚醒帶來的錯誤。而Lock和Condition的幫我們解決了這個問題。一個鎖內部可以有多個Condition.那麼同一個鎖內可以多個condition實現模塊

之間的切換,如上面的例子中A1再執行完之後,通過B對應的Condtion.signal只可以喚醒B對應的線程。我們可以看看Condition的API中的例子,阻塞隊列的簡單實現:

首先我們看看傳統的通信技術實現簡單的阻塞隊列,有何弊端?

package com.lipeng;

import java.util.Random;


public class BoundedBuffer1 <T>{
	private Object[] objs=new Object[100];
	private int length;
	private int putIndex=0;//存指針
	private int getIndex=0;//取指針
	/**
	 * 存放元素,從頭到尾,再反覆從頭到尾
	 * @param t
	 */
	public synchronized void put(T t)
	{
		//如果已經放滿了,就等待。
		while(length==objs.length)//如果N個線程在這兒等待, 其中一個線程被喚醒後,執行下面的代碼,35行this.notify本意是喚醒取線程取數據,但其實可能喚醒存線程。
		{
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		objs[putIndex]=t; //在隊尾插入元素
		length++;  //長度加1,
		putIndex++;
		if(putIndex==objs.length)
		{
			//注意不是放滿了才從起始處放,而是存放指針到隊尾了再從頭開始。
			putIndex=0;
		}
		this.notify();
		
	}
	/**
	 * 取元素,從頭到尾取,在如此反覆。
	 * @return
	 */
	public synchronized T get()
	{
		while(length==0)
		{
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		T t=(T) objs[getIndex];
		length--;
		getIndex++;
		if(getIndex==objs.length)
		{
			getIndex=0;
		}
		this.notify();
		return t;
	}
	
	public static void main(String[] args) {
		final BoundedBuffer1<Integer> bb=new BoundedBuffer1<Integer>();
		Runnable getRun=new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<10;i++)
				{
					synchronized (bb) { //這裏加synchronized只是爲了讓讀取數據和打印數據保持完整性,做演示用用
						Integer data=bb.get();
						System.out.println(Thread.currentThread().getName()+"  讀取元素------   "+data);
					}
					
				}
			}
		};
		Runnable putRun=new Runnable() {
			
			@Override
			public void run() {
				for(int i=0;i<10;i++)
				{
					synchronized (bb) {//這裏加synchronized只是爲了讓存放數據和打印數據保持完整性,做演示用用
						Integer data=new Random().nextInt(100);
						bb.put(data);
						System.out.println(Thread.currentThread().getName()+"  放入---------------------------   "+data);
					}
				}
			}
		};
		System.out.println("***********************");
		for(int i=0;i<10;i++)
		{
			new Thread(getRun).start();
			new Thread(putRun).start();
		}
	}
}






















我們再來看Condition是如何幫我們實現的?

package com.lipeng;

import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class BoundedBuffer2 <T>{
	private Object[] objs=new Object[100];
	private int length;
	private int putIndex=0;//存指針
	private int getIndex=0;//取指針
	
	private Lock lock=new ReentrantLock();
	private Condition putCon=lock.newCondition();//存放條件
	private Condition getCon=lock.newCondition();// 取條件
	
	/**
	 * 存放元素,從頭到尾,再反覆從頭到尾
	 * @param t
	 */
	public void put(T t)
	{
		try {
			lock.lock();
			//如果已經放滿了,就等待。
			while(length==objs.length)//如果N個線程在這兒等待, 其中一個線程被喚醒後,執行下面的代碼,35行this.notify本意是喚醒取線程取數據,但其實可能喚醒存線程。
			{
				try {
					putCon.await();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			objs[putIndex]=t; //在隊尾插入元素
			length++;  //長度加1,
			putIndex++;
			if(putIndex==objs.length)
			{
				//注意不是放滿了才從起始處放,而是存放指針到隊尾了再從頭開始。
				putIndex=0;
			}
			getCon.signal();
			System.out.println(Thread.currentThread().getName()+"  放入---------------------------   "+t);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
		
	}
	/**
	 * 取元素,從頭到尾取,在如此反覆。
	 * @return
	 */
	public T get()
	{
		try {
			lock.lock();
			while(length==0)
			{
				try {
					getCon.await();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			T t=(T) objs[getIndex];
			length--;
			getIndex++;
			if(getIndex==objs.length)
			{
				getIndex=0;
			}
			putCon.signal();
			System.out.println(Thread.currentThread().getName()+"  讀取元素------   "+t);
			return t;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}finally{
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		final BoundedBuffer2<Integer> bb=new BoundedBuffer2<Integer>();
		Runnable getRun=new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<10;i++)
				{
					bb.get();
					
				}
			}
		};
		Runnable putRun=new Runnable() {
			
			@Override
			public void run() {
				for(int i=0;i<10;i++)
				{
					Integer data=new Random().nextInt(100);
					bb.put(data);
				}
			}
		};
		System.out.println("***********************");
		for(int i=0;i<10;i++)
		{
			new Thread(getRun).start();
			new Thread(putRun).start();
		}
	}
}






















注意事項:

Lock(包括讀寫鎖)+Condition看起來更加面向對象,也似乎提高了性能*(因爲我沒驗證過,哈哈)也更加嚴謹,但我認爲如果傳統的通信方法夠用,沒必要使用它。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章