高並發編程-04-線程的安全性
1,什麽是線程安全?
線程安全,有兩個關鍵詞,“共享”和“可變”。
共享是指可以被多個線程同時訪問;
可變是指變量的值在生命周期內是可以變化的;
一個對象是否需要線程安全的,取決於它是否被多個線程訪問;
而如何保證一個對象的線程安全,則需要采用同步機制來協同對對象可變狀態的訪問。
下面,我們給線程安全下一個明確的定義:當多個線程訪問這個類時,如果這個類始終都能表現出正確的行為,那麽就稱這個類是線程安全的。
這裏有我經常喜歡問面試者的一個問題,Servlet是線程安全的嗎?
其實這個問題,並沒有標準答案。這個問題的關鍵是看Servlet是有狀態的,還是無狀態的。什麽叫有狀態的?簡單來說,就是在Servlet中定義了一個全局變量,然後在相關的doService或doGet、doPost等方法中,對這個全局變量做了更新操作,那麽這個時候就說明是有狀態的,反之就是無狀態的。而當有狀態時,就需要考慮線程安全的處理,否則不需要。
來,我們給大家一個結論:
無狀態的對象一定是線程安全的。
多線程環境下,多個線程共享一個資源,且進行的不是原子性操作,這個時候就要考慮線程的安全控制問題
2,原子性
我們在之前的文章也說過這個問題,比如一個簡單的操作,count++,其實是不具備原子性的,因為這個步驟實際會被拆分為三個步驟,即 讀取---修改---寫入,而這三個步驟有可能在某個時刻因CPU時間片的切換問題,而只執行其中一兩個步驟,這就不具備原子性。
在JDK中,為了解決這個問題,java.util.concurrent.atomic包提供了很多的類,來保證數據操作的原子性,比如我們之前的程序可以修改為
public class System {
private AtomicInteger integer = new AtomicInteger(0);
public int getCount(){
return integer.incrementAndGet();
}
}
3,鎖機制
通過上述的描述,我們知道,當一個類包含了一個有狀態的變量時,只要采用一個線程安全的對象就可以搞定這個問題,但是,在這裏要提醒大家一點,如果一個類包含了多個有狀態的變量,這個時候可不是單純的加幾個線程安全的對象就可以搞定問題。
一個判斷標準就是,只要程序中存在“先判斷,再更新”,那麽就要保證這兩個操作在一個原子操作裏面,才能保證線程安全。
關於java鎖機制的一些特點,我在這跟大家羅列下:
內置鎖、監視鎖、互斥鎖、可重入鎖都是在這個鎖的特點
內置鎖、監視鎖:這兩個說的是一個意思,java的每一個對象都可以用來做內置鎖,也就是為什麽我們的wait、notify方法定義在Object類的原因。
互斥鎖:表示最多只有一個線程可以持有這把鎖。
可重入鎖:是指當線程A請求一個由線程B持有的鎖時,線程B會進入阻塞狀態;而如果線程A如果再訪問另一段代碼,而這個代碼的鎖是已經被線程A持有的,這個時候請求是可以成功的,這就叫可重入。
跟大家講講這個鎖的實現原理:
JVM為每個鎖設置兩個屬性,獲取計數值和所有者線程,當計數值為0時,這個鎖就被認為是沒有被任何線程持有,當線程請求一個未被持有的鎖時,JVM將記錄鎖的持有者,並且計數值+1。如果同一個線程再次獲取這個鎖,則計數值將遞增,而當線程退出同步代碼塊時,計數器會相應遞減,當計數值為0,這個鎖將被釋放。
大家如果有問題,歡迎留言,留言我也不一定有時間回答。。。。。。
binary viewer 可以查看字節碼文件
通過javap -verbose *.class 也可以看到字節碼內容
高並發編程-04-線程的安全性