一. 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看起來更加面向對象,也似乎提高了性能*(因爲我沒驗證過,哈哈)也更加嚴謹,但我認爲如果傳統的通信方法夠用,沒必要使用它。