java中多線程的同步分析

多線程的出現提高了CPU的利用率,也提供給了我們更多創造好玩的遊戲、軟件的思路和方法。但是另一方面,在使用多線程的時候,我們要格外注意多線程帶來了潛在的不安全性,本文先介紹一下線程中最基本的同步問題。

在使用多線程的時候,我們往往會採用實現Runnable接口的方式創建一個類,這時實現的run()方法中的代碼則是多個線程所共用的,這些代碼中也可能會有共享的數據,當代碼語句很多時,對於裏面某些需要安全判斷的語句來說,CPU的切換很可能會造成一種安全問題。比如代碼裏面有一個判斷語句

if (flag > 0) {
	flag--;
	System.out.println(flag);		
}
就單線程模式來講,輸出的flag是不可能出現負數的情況的,然而一旦涉及到多線程,像上面說的,多個線程裏面都含有這些代碼,我們假使當flag=1的時候,這時一個線程進入這個判斷,這時是滿足flag>0的條件的,然而當線程進入語句塊但是還沒有執行flag--這行代碼的時候,CPU一下子將執行權給了另外一個線程,注意,這個時候flag還是等於1,所以另外一個線程順利進入了if語句塊。不用多說,其他別的線程也能這樣進來,這樣,當運行輸出語句的時候,flag的值早就是一個負數了。(當然,因爲線程執行這幾句語句的速度非常快,所以出現這個問題的概率也很低,不過我們可以在flag--之前寫一句Thread.sleep(50),讓線程停一會,這樣線程也就更容易卡在這個地方了)

瞭解到線程中潛在的安全問題後,我們就要採取java中提供給我們的處理安全問題的方法了,java中爲了解決多線程的安全問題,提供了一種方法:同步。

同步的關鍵字是synchronized,同步語句塊的一般形式是

synchronized ( /*對象鎖*/ ) {
	//需要被同步的代碼
}
其中對象鎖是一個對象,也就是API文檔中說的對象監視器,進入代碼塊的線程需要擁有此對象監視器。比如現在程序中有兩個線程正在同時運行,且兩個線程都即將要執行同步語句塊,這時還沒有哪一個線程拿到這個對象鎖,當其中一個線程進入這個語句塊的時候,此線程將首先拿到鎖,在它執行同步代碼塊裏面的代碼的時候,即使CPU將執行權切到其他線程,其他線程也會因爲沒有拿到鎖而進不去同步代碼塊。而當拿到鎖的那個線程離開此代碼塊時將釋放對象鎖,此時其他線程才能進入同步代碼塊。

剛開始接觸synchronized可能會糾結寫哪一個對象鎖。其實對象鎖是可以任意選擇的,甚至可以new一個Object當作這個鎖,但是需要注意的是必須要保證多個線程共用這個對象鎖,比如

public void run() {
	Object obj = new Object();
	synchronized(obj){
		//代碼
	}
}
因爲每個線程都有自己的run方法,所以每個線程進入自己的run方法的時候會擁有自己的obj,這樣synchronized就顯得毫無意義。

當然也可以在程序中寫多個同步代碼塊,當同步代碼塊的對象鎖是同一個鎖的時候,則可以把這些代碼塊看成是一體的,因爲無論執行哪一個代碼塊,其他線程將無法進入其它的代碼塊。

另外synchronized關鍵字也可以寫在函數名的前面,這時這個函數將是一個同步函數,函數可以看作是一個大的同步代碼塊,此代碼塊同步鎖是this。其實是很好理解的,因爲當程序簡單到只有一個類的時候,也就只有一個能共享的對象即this,而且函數的調用者就是this。那麼,大家可能會想到如果這個函數是靜態的,那麼函數的鎖又是什麼呢?此時,這個鎖是此類的字節碼文件對象,可以通過類名.class或this.getClass()得到,這也是一個類獨一無二的對象。

爲什麼要出現同步呢,綜上所述,個人感覺同步就好像是一個“保護罩”一樣的,將可能潛在安全問題的代碼保護起來,只允許一個線程去訪問,這樣避免多線程同時訪問一個數據而出現安全問題。

發佈了54 篇原創文章 · 獲贊 14 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章