線程安全問題
在單線程中不會出現線程安全問題,而多線程編程中,如果多個線程同時操作同一個資源,這種資源可以是各種類型的的資源:一個變量、一個對象、一個文件、一個數據庫表等,由於每個線程執行的過程是不可控的,比如兩個線程同時檢查某個文件是否存在,如果存在則文件數+1,不存在則創建新文件,最後產生的結果很可能創建兩個新文件,原因就是第一個線程檢測到文件不存在時,在創建新文件前,第二個線程也檢測到文件不存在,所以線程二也要新建一個文件,最後的結果就是新建了兩個文件,與初衷不符,這個就是線程安全問題。
解決線程同步問題的兩個方法
在java中有兩種機制可以防止線程安全的發生,Java語言提供了一個synchronized關鍵字來解決這問題,同時在Java SE5.0引入了Lock鎖對象的相關類,接下來分別介紹這兩種方法。
通過synchronied關鍵字的方式
在Java中,可以使用synchronized關鍵字來標記一個方法或者代碼塊,當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便獲得了該對象的鎖,其他線程暫時無法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程纔會釋放該對象的鎖,其他線程才能執行這個方法或者代碼塊。
synchronized原理
JVM基於進入和退出Monitor對象來實現代碼塊同步和方法同步,兩者實現細節不同,但本質上都是對一個對象的監視器(monitor)的獲取。任意一個對象都擁有自己的監視器,當同步代碼塊或同步方法時,執行方法的線程必須先獲得該對象的監視器才能進入同步塊或同步方法,沒有獲取到監視器的線程將會被阻塞,並進入同步隊列,狀態變爲BLOCKED。當成功獲取監視器的線程釋放了鎖後,會喚醒阻塞在同步隊列的線程,使其重新嘗試對監視器的獲取。
代碼塊同步:在編譯後通過將monitorenter指令插入到同步代碼塊的開始處,將monitorexit指令插入到方法結束處和異常處,任何一個對象都有一個monitor與之關聯,線程執行monitorenter指令時,會嘗試獲取對象對應的monitor的所有權,即嘗試獲得對象的鎖。
方法同步:synchronized方法在method_info結構有ACC_synchronized標記,線程執行時會識別該標記,獲取對應的鎖,實現方法同步。
兩種使用synchronized關鍵字的實現方式
- synchronized修飾方法
public synchronized void method{
//需要同步的代碼
}
- synchronized修飾代碼塊
Object obj = new Object();
synchronized(obj){
//需要同步的代碼
}
通過鎖(Lock)對象的方式
Java SE5.0之後併發包中新增了Lock接口用來實現鎖的功能,它提供了與synchronized關鍵字類似的同步功能,只是在使用時需要顯式地獲取和釋放鎖,缺點就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性,可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具備的同步特性。
ReentrantLock(重入鎖)是支持重新進入的鎖,它表示該鎖能夠支持一個線程對資源的重複加鎖,也就是說在調用lock()方法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞,同時還支持獲取鎖的公平性和非公平性。這裏的公平是在絕對時間上,先對鎖進行獲取的請求一定先被滿足,那麼這個鎖是公平鎖,反之,是不公平的。
使用ReentrantLock的實現方式
ReentrantLock lock = new ReentrantLock(); //參數默認false,不公平鎖
ReentrantLock lock = new ReentrantLock(true); //公平鎖
lock.lock(); //如果被其它資源鎖定,會在此等待鎖釋放,達到暫停的效果
try {
//操作
} finally {
lock.unlock(); //釋放鎖
}
synchronized和ReentrantLock的區別
- Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現。
- synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖。
- Lock可以讓等待鎖的線程響應中斷,而synchronized不可以,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷。
- 通過Lock可以知道是否成功獲取到鎖,而synchronized卻無法做到。
- Lock可以提高多個線程進行讀操作的效率。