Java 線程與併發研究系列四(多線程)

當使用多個線程來同時運行多個任務時,有時候需要對某項共享資源進行操作,怎樣使得一個任務不會干涉另外一個任務呢?這時候就需要

使用鎖來使得資源的訪問變得互斥,也就是同時只能有一個任務對共享資源進程訪問。

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

方法,這是因爲,可能由於其他原因,還有其他任務也在等待這輛車的同一個鎖,而這個鎖被喚醒時,可能會導致其他任務獲取這個鎖,從而

導致這個任務繼續被掛起。

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