當使用多個線程來同時運行多個任務時,有時候需要對某項共享資源進行操作,怎樣使得一個任務不會干涉另外一個任務呢?這時候就需要
使用鎖來使得資源的訪問變得互斥,也就是同時只能有一個任務對共享資源進程訪問。
Java中能夠通過Object的wait()和notify()方法來安全的訪問共享資源。Java SE5的併發庫還提供了await()和signal()方法的Condition對象來實
現資源的安全訪問。
下面我們來了解一下一些常用的線程同步相關方法
notify()和notifyAll()的不同,notify():在衆多等待同一個鎖的任務中,只有一個被喚醒,所以如果使用notify()時,就必須保證被喚醒的是恰當
的任務。notify一般用於具有喚醒同步塊的對象。notifyAll()是喚醒所有正在等待同一個鎖的任務。
wait()方法的作用是將調用該方法的線程掛起,和sleep(),yield()這些方法不同的是,在調用wait方法後,該對象上的鎖會被釋放掉,也就是
在此聲明:我已經做完所有能做的事,因此我在這裏等待,但是我希望其他的synchronized操作在條件適合的情況下能夠執行。
wait,notify,notifyAll,這些方法的是基類Object的一部分,而不是屬於Thread的一部分,我們可能會感到奇怪,爲什麼針對線程的功能卻
作爲基類的一部分來實現,這是因爲這些操作的鎖也是所有對象的一部分,所以你可以在任何的同步控制方法或者同步控制塊中調用wait()
notify(),notifyAll(),如果在非同步控制方法中調用這些方法時,程序能夠編譯通過,但運行時會拋出IllegalMonitorStateException異常,也
就是在調用這些方法的任務在調用這些方法前必須擁有對象鎖。
下面我們通過一個例子來熟悉這些方法的使用。
現在有一個場景是:一輛車,需要多次進行塗蠟和拋光,在塗蠟之前必須進行拋光,在拋光之前必須進行塗蠟,剛開始肯定是從塗蠟開始
然後進行拋光,然後再塗蠟,以此循環。下面給出這個場景的代碼:
public class MyTest{
public static volatile boolean isStop = false;
public static void main(String[] args) throws InterruptedException{
Car car = new Car();
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(new Wax(car));
exec.submit(new Buff(car));
TimeUnit.SECONDS.sleep(5);
isStop = true;
exec.shutdownNow();
}
}
class Car{
private boolean waxOn= false;//塗蠟的標誌位,ture表示正在塗蠟
/**
* 塗蠟*/
public synchronized void wax(){
waxOn = true;
notifyAll();
}
/**
* 拋光*/
public synchronized void buff(){
waxOn = false;
notifyAll();
}
/**
* 等待塗蠟*/
public synchronized void waitForWax()throws InterruptedException{
while(waxOn == false){
wait();
}
}
/**
* 等待拋光*/
public synchronized void waitForBuff()throws InterruptedException{
while(waxOn == true){
wait();
}
}
}
class Wax implements Runnable{
Car car;
public Wax(Car car){
this.car = car;
}
@Override
public void run() {
while(!MyTest.isStop){
try {
car.waitForBuff();
TimeUnit.MILLISECONDS.sleep(200);//塗蠟所需時間
System.out.println("正在塗蠟");
car.wax();
} catch (InterruptedException e) {
System.out.println("塗蠟完畢");
}
}
System.out.println("結束塗蠟");
}
}
class Buff implements Runnable{
Car car;
public Buff(Car car){
this.car = car;
}
@Override
public void run() {
while(!MyTest.isStop){
try {
car.waitForWax();
TimeUnit.MILLISECONDS.sleep(200);//拋光所需時間
System.out.println("正在拋光");
car.buff();
} catch (InterruptedException e) {
System.out.println("拋光完畢");
}
}
System.out.println("結束拋光");
}
}
上面的代碼我們就實現了這個場景,最先開始提交塗蠟的任務,然後提交拋光的任務,確保任務是從塗蠟開始的。waxOn這個變量就是塗蠟
和拋光的標誌爲false時表示正在進行拋光,等待塗蠟,爲true時表示正在進行塗蠟,等待拋光。塗蠟時,先調用waitForBuff()方法將拋光的線
程掛起,然後調用wax()方法進行塗蠟,在這個方法中將waxOn標誌位設爲true,塗蠟完畢後使用notifyAll()喚醒正在等待這輛車的拋光的任務。
在拋光時,先調用waitForWax()方法將塗蠟的線程掛起,然後調用buff()方法進行拋光,並將waxOn標誌位設爲false,拋光完畢後使用notifyAll()
方法喚醒正在等待這輛車的塗蠟任務。
讓這個過程持續5秒,然後調用shutdownNow(),這個方法會調用由他控制的線程的interrupt()方法。至於爲什麼要使用一個while循環包圍wait
方法,這是因爲,可能由於其他原因,還有其他任務也在等待這輛車的同一個鎖,而這個鎖被喚醒時,可能會導致其他任務獲取這個鎖,從而
導致這個任務繼續被掛起。