1. 程式人生 > >淺談執行緒安全與sychronized

淺談執行緒安全與sychronized

使用多執行緒處理相關業務,在一定程度上能夠獲得更高的執行效率,提高程式效能,但是,如果我們在寫多執行緒程式時,不加強注意,容易出現數據不一致性,也就是我們常說的 "執行緒安全" 問題。執行緒安全是我們在設計多執行緒程式時必須保證的一點,如果我們寫出的程式資料正確性不能得到保證,即使程式效能得到提高,也是毫無意義的。

非執行緒安全在什麼時候會出現呢?

當多個執行緒對同一物件例項進行併發訪問時,可能得到錯誤的結果,這就是執行緒不安全的。相反,無論多少執行緒對同一物件例項進行併發訪問,都能得到一致的結果,這就是執行緒安全的。

下面我們給出一個經典的執行緒不安全的例子:

我們在當前程式中定義來了一個instance物件,對它執行++操作,按照正常邏輯,它返回的結果應該是200,然而結果總是差強人意。這裡,我們可以看到返回的結果是144,大家可以多執行幾次,看看有什麼不同,由於instance++不是原子性操作,因此,我們的程式存在安全性問題。那麼怎樣才能使結果變為200呢?我們做一點小的修改:

大家可以發現,結果是我們預期值200了。這裡我們在instance++操作上加了一段synchronized語句塊,好神奇,它為什麼就能解決執行緒安全性問題呢。

sychronized關鍵字,實現執行緒間的同步,它的作用就是給同步程式碼加鎖,每一次只能有一個執行緒進入同步塊,從而保證執行緒安全性問題。在上一段程式碼中,當執行緒A在對instance變數進行讀寫的時候,執行緒B只能等待執行緒A執行完成,才能對instance變數進行++操作。

sychronized大致有三種用法,這裡詳細講解一下:

1.指定物件加鎖。當執行緒訪問同步塊時,首先獲取加鎖物件,如果物件鎖被其它執行緒持有,則進行等待。

2.作用於例項方法上。給當前例項加鎖,訪問同步程式碼時,首先獲得當前例項的鎖。

3.作用於靜態方法上。靜態方法加鎖,給當前class加鎖,訪問同步程式碼時,需要獲得當前類的鎖。

大家現在應該立馬就能想到,上述程式碼,我們使用的就是第一種方式,給指定物件加鎖。我們加鎖的物件就是當前類的例項物件,當我們對同一例項物件進行訪問時,一次只有一個執行緒獲得物件鎖。關於另外兩種加鎖方式,我們修改一下上述程式碼來理解它們:

例項物件加鎖:

結果符合預期,我們把之前的同步塊改成了例項方法,該方法,鎖住的是SychronizedDemo的例項物件,和之前同步塊中this的效果一樣。當執行緒需要訪問同步方法時,必須先獲得當前類的例項物件,否則,只能進行等待。

靜態方法加鎖:

結果符合預期,大家可以看到,main()方法中,我們new 了兩個例項物件,但是結果竟然是正確的。因為我們使用的是靜態方法加鎖,這樣鎖住的就是當前類物件,無論我們定義多少個例項物件,結果都是一樣的。

sychronized的另外一個特性:鎖重入。在使用sychronized關鍵字時,當一個執行緒獲得物件鎖後,再次請求此物件鎖時是可以再次獲得該物件鎖的。簡單來說就是,在一個sychronized方法或同步塊中呼叫當前類的其它sychronized方法,是能夠得到鎖的。大家可以想一下,當前執行緒在還未釋放鎖時,再一次請求鎖,如果不可鎖重入的話,就會形成死鎖,這是大家最不想看到的。關於鎖重入,大家可以寫三個sychronized修飾的方法,然後相互呼叫,看看是否可以正常執行。限於篇幅,這裡我就不貼出程式碼了。

sychronized關鍵字,大家是不是覺得它在解決執行緒安全時,特別的簡單,無需太多的操作。大家再思考一下,如果我們現在有兩個方法,第一個方法執行寫操作,第二個方法執行讀操作,當我們使用sychronized關鍵字修飾時,那麼我們多個執行緒同時執行讀方法,也會相互等待,這是我們不願意看到的。我們只希望讀寫互斥,寫寫互斥,讀讀不互斥,那麼我們就需要了解另外一個重入鎖Lock了,下一節,我們關於Lock的用法進行詳細講解。