單線程中不會出現線程安全問題,而在多線程編程中,有可能會出現同時訪問同一個資源的情況。
由於每個線程執行的過程是不可控的,所以很可能導致最終的結果與實際上的願望相違背或者直接導致程序出錯。
線程安全問題,即多個線程同時訪問一個資源時,會導致程序運行結果並不是想看到的結果。
那麼一般來說,是如何解決線程安全問題的呢?
基本上所有的併發模式在解決線程安全問題時,都採用“序列化訪問臨界資源”的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱作同步互斥訪問。
在Java中,提供了兩種方式來實現同步互斥訪問:synchronized和Lock
在Java中,每一個對象都擁有一個鎖標記(monitor),也稱爲監視器,多線程同時訪問某個對象時,線程只有獲取了該對象的鎖才能訪問。
在Java中,可以使用synchronized關鍵字來標記一個方法或者代碼塊,當某個線程調用該對象的synchronized方法或者訪問 synchronized代碼塊時,這個線程便獲得了該對象的鎖,其他線程暫時無法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程 纔會釋放該對象的鎖,其他線程才能執行這個方法或者代碼塊。
當一個線程正在訪問一個對象的synchronized方法,那麼其他線程能訪問該對象的非synchronized方法。
如果一個線程A需要訪問對象object1的synchronized方法fun1,另外一個線程B需要訪問對象object2的 synchronized方法fun1,即使object1和object2是同一類型),也不會產生線程安全問題,因爲他們訪問的是不同的對象,所以不 存在互斥問題。
synchronized代碼塊類似於以下這種形式
synchronized(synObject) {
}
synObject可以是this,代表獲取當前對象的鎖,也可以是類中的一個屬性,代表獲取該屬性的鎖。
另外,每個類也會有一個鎖,它可以用來控制對static數據成員的併發訪問。
並且如果一個線程執行一個對象的非static synchronized方法,另外一個線程需要執行這個對象所屬類的static synchronized方法,此時不會發生互斥現象,因爲訪問static synchronized方法佔用的是類鎖,而訪問非static synchronized方法佔用的是對象鎖,所以不存在互斥現象。
有一點要注意:對於synchronized方法或者synchronized代碼塊,當出現異常時,JVM會自動釋放當前線程佔用的鎖,因此不會由於異常導致出現死鎖現象。