一、什麼時候會出現執行緒安全問題?
無執行緒安全問題:
1、在單執行緒中不會出現執行緒安全問題
2、java領域的執行緒安全問題,通常是全域性變數或靜態變數引起的,若每個執行緒對共享變數只有讀操作,一般來說該變數是執行緒安全的,若有寫操作,則需要考慮執行緒安全問題。
3、當多個執行緒執行一個方法,方法內部的區域性變數並不是臨界資源,因為方法是在棧上執行的,而Java棧是執行緒私有的,因此不會產生執行緒安全問題。
執行緒安全問題:
1、有多個執行緒時,同時訪問同一個資源(一個物件,物件中的屬性,一個檔案,一個資料庫等),會導致程式執行結果並不是想要的。
舉個簡單的例子:
現在有兩個執行緒分別從網路上讀取資料,然後插入一張資料庫表中,要求不能插入重複的資料。那麼必然在插入資料的過程中存在兩個操作:
1)檢查資料庫中是否存在該條資料;
2)如果存在,則不插入;如果不存在,則插入到資料庫中。
假如兩個執行緒分別用thread-1和thread-2表示,某一時刻,thread-1和thread-2都讀取到了資料X,那麼可能會發生這種情況:
thread-1去檢查資料庫中是否存在資料X,然後thread-2也接著去檢查資料庫中是否存在資料X,結果兩個執行緒檢查的結果都是資料庫中不存在資料X,那麼兩個執行緒都分別將資料X插入資料庫表當中。
這裡面,這個資源被稱為:臨界資源(也有稱為共享資源)。
二、如何解決執行緒安全問題?
通常是在訪問臨界資源的程式碼前面加鎖,當訪問完臨界資源後釋放鎖,讓其他執行緒繼續訪問。
2.1 單機
單機多執行緒情況,可以透過 synchronized 或者 Lock 進行加鎖,是Java中提供的兩種方式來實現同步互斥訪問。
2.2 分散式系統
現在大部分應用都是採用分散式的方式進行部署,屬於多程序的情況,這可以透過分散式鎖、原子操作來解決。a. 分散式鎖的實現方式有
- 基於資料庫的分散式鎖 分散式鎖三種實現方式及對比
- 基於快取(Redis)的分散式鎖
- 基於 Zookeeper 的分散式鎖
- 單命令:把多個操作在Redis中實現成一個操作,也就是單命令操作。Redis是使用單執行緒來序列處理客戶端的請求操作命令的,所以,當Redis執行某個命令操作時,其他命令是無法執行的,這相當於命令操作是互斥執行的。
- Lua指令碼:把多個操作寫到一個Lua指令碼中,以原子性方式執行單個Lua指令碼。
參考資料
多執行緒的安全問題
redis 高併發分散式鎖實現
Java併發程式設計:Lock