您好,登錄后才能下訂單哦!
本篇內容介紹了“線程的安全性是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
這里我們寫一個簡單的線程安全類,簡單到什么地步呢?如下所示
public class SafeDemo { public int sum(int n, int m){ return n + m; } }
就是這么簡單,我們說這個類是線程安全的
為啥安全呢?
因為這個類沒有狀態,即無狀態類;
只有局部變量n,m,而這些局部變量是存在于棧中的,棧是每個線程獨有的,不跟其他線程共享,堆才共享
所以每個線程操作sum時,對應的n,m只有自己可見,當然就安全了
好了,通過上面的例子,我們知道了什么是線程安全類,那本節的內容就到此結束了,再見
上面的例子,我們舉了一個無狀態類,接下來我們添加一個狀態試試
加一個狀態變量(靜態屬性),代碼如下
public class UnSafeDemo { static int a = 0; public static void main(String[] args) throws InterruptedException { // 線程1 new Thread(()-> { for(int j=0;j<100000;j++){ a++; } }).start(); // 線程2 new Thread(()-> { for(int j=0;j<100000;j++){ a++; } }).start(); Thread.sleep(3000); // 這里不是每次運行都會輸出200,000 System.out.println(a); } }
上面我們創建了兩個線程,每個線程都執行10萬次的自增操作
但是因為自增不是原子操作,實際分三步:讀-改-寫
此時如果兩個線程同時讀到相同的值,則累加次數就會少一次
這種在并發編程中,由于不恰當的執行時序而出現不正確的結果的情況,叫做競態條件
如下圖所示:
期望的是正常執行,每個線程交替執行
結果卻有可能是不正常的,如下
這時我們就可以說,上面加的這個狀態是不安全的,結果就是整個類也是不安全的
不安全的狀態有二:
可變狀態(變量):非final修飾的變量
共享狀態(變量):非局部變量
像上面這個例子,狀態就同時屬于可變狀態和共享狀態
那要怎么確保安全:
同步:synchronized、volatile、顯式鎖、原子變量(比如AtomicInteger)
不可變變量:final(都不能改了,當然安全了)
不共享變量:不在多線程中共享變量(即局部變量)
PS:代碼的封裝性越好,訪問可變變量的代碼塊越少,越容易確保線程安全
這里的自增我們就可以用同步中的原子變量來解決
關于原子變量的細節,后面章節再介紹,這里只需要知道,原子變量內部的操作是原子操作就可以了
修改后的代碼如下:
public class SafeDemo { static final AtomicInteger a = new AtomicInteger(0); // static int a = 0; public static void main(String[] args) throws InterruptedException { // 線程1 new Thread(()-> { for(int j=0;j<100000;j++){ // 這里的自增是原子操作 a.incrementAndGet(); } }).start(); // 線程2 new Thread(()-> { for(int j=0;j<100000;j++){ // 這里的自增是原子操作 a.incrementAndGet(); } }).start(); Thread.sleep(3000); System.out.println(a.get()); } }
可以看到,加了AtomicInteger.incrementAndGet()方法,這個方法是原子操作
這時,不管怎么運行,都是輸出200,000
上面我們加了一個狀態變量,可以用原子變量來保證線程安全
那如果是多個狀態變量呢?此時就算用了原子變量也不行了
因為原子變量只是保證它內部是原子操作,但是當多個原子變量放到一起組合操作時,他們之間又存在競態條件了,就又不是原子操作了
競態條件:并發編程中,由于不恰當的執行時序而出現不正確的結果的情況,就是競態條件(重復陳述ing,加深記憶)
代碼如下:
public class UnSafeDemo2 { static final AtomicInteger a = new AtomicInteger(0); static final AtomicInteger b = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { new Thread(()-> { for(int j=0;j<10000;j++){ a.incrementAndGet(); b.incrementAndGet(); if(a.get()!=b.get()){ // 理想狀態的話,不會運行到這里,因為a和b是一起自增的 // 但是大部分時候都是不正常的,因為a和b各自是原子操作,但是放到一起就不是原子操作了 System.out.println(1); } } }).start(); new Thread(()-> { for(int j=0;j<10000;j++){ a.incrementAndGet(); b.incrementAndGet(); if(a.get()!=b.get()){ // 理想狀態的話,不會運行到這里,因為a和b是一起自增的 // 但是大部分時候都是不正常的,因為a和b各自是原子操作,但是放到一起就不是原子操作了 System.out.println(2); } } }).start(); } }
上面多次運行,會發現基本上每次都會打印1和2,就是因為這兩個線程之間存在競態條件
那怎么解決呢?
上鎖
代碼如下:
public class UnSafeDemo2 { static final AtomicInteger a = new AtomicInteger(0); static final AtomicInteger b = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { // 單獨創建一個對象,用來充當鎖 UnSafeDemo2 unSafeDemo2 = new UnSafeDemo2(); new Thread(()-> { for(int j=0;j<10000;j++){ // 這里加了鎖 synchronized (unSafeDemo2){ a.incrementAndGet(); b.incrementAndGet(); if(a.get()!=b.get()){ // 現在肯定是理想狀態,不會運行到這里 System.out.println(1); } } } }).start(); new Thread(()-> { for(int j=0;j<10000;j++){ // 這里加了鎖 synchronized (unSafeDemo2){ a.incrementAndGet(); b.incrementAndGet(); if(a.get()!=b.get()){ // 現在肯定是理想狀態,不會運行到這里 System.out.println(2); } } } }).start(); } }
這里用到的鎖為內置鎖,還有很多其他鎖,這里就不展開了(后面章節再介紹)
這里要注意:同步代碼必須上同一個鎖才有用,比如上面的例子,兩個線程都是上的unsafeDemo2這個鎖
官人們可以試一下,一個上unsafeDemo2鎖,一個上Object鎖,看會輸出啥
內置鎖也叫監視器鎖
特點:
互斥性:即一個線程持有鎖,其他線程就要等待鎖釋放后才可以獲取鎖
可重入性:如果某個線程嘗試去獲取一個鎖,而這個鎖之前就是這個線程所持有的,那么這個線程就可以再次獲取到鎖
縮小鎖的范圍
將耗時長的操作(前提是操作與狀態無關),放到同步之外的代碼塊
跟狀態有關的方法都需要上鎖:操作麻煩,其實就是類的每個方法都需要上鎖,如果后面添加了一個方法,忘記加鎖,那還是有安全問題(比如被官人們遺棄的Vector)
性能問題:整個方法都上鎖,性能很低,尤其是一些耗時操作,比如網絡IO這種容易阻塞的操作
避免了死鎖:比如一個子類繼承父類的synchronized方法,并顯示調用父類的synchronized方法,如果不可重入,那么在子類中獲取的鎖,調用子類的fun方法是沒問題的,但是調用父類的fun方法時,會提示上了鎖,從而被阻塞,此時就會死鎖(自己持有鎖,還有再去獲取鎖,但是又獲取不到)
好處:
缺點:
解決:
好了,差不多先這些吧,后面還有太多東西了,慢慢來吧。
畢竟我們都一大把年紀了,身體要緊吶。
懶了懶了,直接貼圖了(敲的腦仁疼),圖做的不是很好,不過應該能看懂,望見諒哈
“線程的安全性是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。