成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

使用了Synchronized,竟然還有線程安全問題!

開發 項目管理
線程安全問題一直是系統亙古不變的痛點。這不,最近在項目中發了一個錯誤使用線程同步的案例。表面上看已經使用了同步機制,一切歲月靜好,但實際上線程同步卻毫無作用。

[[412969]]

本文轉載自微信公眾號「程序新視界」,作者丑胖俠二師兄。轉載本文請聯系程序新視界公眾號。

實戰中受過的傷,才能領悟的更透徹,二師兄帶你分析實戰案例。

線程安全問題一直是系統亙古不變的痛點。這不,最近在項目中發了一個錯誤使用線程同步的案例。表面上看已經使用了同步機制,一切歲月靜好,但實際上線程同步卻毫無作用。

關于線程安全的問題,基本上就是在挖坑與填坑之間博弈,這也是為什么面試中線程安全必不可少的原因。下面,就來給大家分析一下這個案例。

有隱患的代碼

先看一個脫敏的代碼實例。代碼要處理的業務邏輯很簡單,就是多線程訪問一個單例對象的成員變量,對其進行自增處理。

SyncTest類實現了Runnable接口,run方法中處理業務邏輯。在run方法中通過synchronized來保證線程安全問題,在main方法中創建一個SyncTest類的對象,兩個線程同時操作這一個對象。

  1. public class SyncTest implements Runnable { 
  2.  
  3.     private Integer count = 0; 
  4.  
  5.     @Override 
  6.     public void run() { 
  7.         synchronized (count) { 
  8.             System.out.println(new Date() + " 開始休眠" + Thread.currentThread().getName()); 
  9.             count++; 
  10.             try { 
  11.                 Thread.sleep(10000); 
  12.                 System.out.println(new Date() + " 結束休眠" + Thread.currentThread().getName()); 
  13.             } catch (InterruptedException e) { 
  14.                 e.printStackTrace(); 
  15.             } 
  16.         } 
  17.     } 
  18.  
  19.     public static void main(String[] args) throws InterruptedException { 
  20.         SyncTest test = new SyncTest(); 
  21.         new Thread(test).start(); 
  22.         Thread.sleep(100); 
  23.         new Thread(test).start(); 
  24.     } 

在上述代碼中,兩個線程訪問SyncTest的同一個對象,并對該對象的count屬性進行自增操作。由于是多線程,那就要保證count++的線程安全。

代碼中使用了synchronized來鎖定代碼塊,進行同步處理。為了演示效果,在處理完業務邏輯對線程進行睡眠。

理想的狀況是第一個線程執行完畢,然后第二個線程才能進入并執行。

表面上看,一切都很完美,下面我們來執行一下程序看看結果。

執行驗證

執行main方法打印結果如下:

  1. Fri Jul 23 22:10:34 CST 2021 開始休眠Thread-0 
  2. Fri Jul 23 22:10:34 CST 2021 開始休眠Thread-1 
  3. Fri Jul 23 22:10:44 CST 2021 結束休眠Thread-0 
  4. Fri Jul 23 22:10:45 CST 2021 結束休眠Thread-1 

正常來說,由于使用了synchronized來進行同步處理,那么第一個線程進入run方法之后,會進行鎖定。先執行“開始休眠”,然后再執行“結束休眠”,最后釋放鎖之后,第二個線程才能夠進入。

但分析上面的日志,會發現兩個線程同時進入了“開始休眠”狀態,也就是說鎖并未起效,線程安全依舊存在問題。下面我們就針對synchronized失效原因進行逐步分析。

synchronized知識回顧

在分析原因之前,我們先來回顧一下synchronized關鍵字的使用。

synchronized關鍵字解決并發問題時通常有三種使用方式:

同步普通方法,鎖的是當前對象;

同步靜態方法,鎖的是當前Class對象;

同步塊,鎖的是()中的對象;

很顯然,上面的場景中,使用的是第三種方式進行鎖定處理。

synchronized實現同步的過程是:JVM通過進入、退出對象監視器(Monitor)來實現對方法、同步塊的同步的。

代碼在編譯時,編譯器會在同步方法調用前加入一個monitor.enter指令,在退出方法和異常處插入monitor.exit的指令。其本質就是對一個對象監視器(Monitor)進行獲取,而這個獲取過程具有排他性從而達到了同一時刻只能一個線程訪問的目的。

原因分析

經過上面基礎知識的鋪墊,我們就來排查分析一下上述代碼的問題。其實,對于這個問題,IDE已經能夠給出提示了。

如果你使用的IDE帶有代碼檢查的插件,synchronized (count)的count上會有如下提示:

Synchronization on a non-final field 'xxx' Inspection info: Reports synchronized statements where the lock expression is a reference to a non-final field. Such statements are unlikely to have useful semantics, as different threads may be locking on different objects even when operating on the same object.

很多人可能會忽視掉這個提示,但它已經明確指出此處代碼有線程安全問題。提示的核心是“同步處理應用在了非final修飾的變量上”。

對于synchronized關鍵字來說,如果加鎖的對象是一個可變的對象,那么當這個變量的引用發生了改變,不同的線程可能鎖定不同的對象,進而都會成功獲得各自的鎖。

用一個圖來回顧一下上述過程:

在上圖中,Thread0在①處進行了鎖定,但鎖定的對象是Integer(0);Thread1中②處也進行鎖定,但此時count已經進行自增,導致Thread1鎖定的是對象Integer(1);也就是說,兩個線程鎖定的對象不是同一個,也就無法保證線程安全了。

解決方案

既然找到了問題的原因,我們就可以有針對性的進行解決,這里用的count屬性很顯然不可能用final進行修飾,不然就無法進行自增處理。這里我們采用對象鎖的方式來進行處理,也就鎖對象為當前this或者說是當前類的實例對象。修改之后的代碼如下:

  1. public class SyncTest implements Runnable { 
  2.  
  3.     private Integer count = 0; 
  4.  
  5.     @Override 
  6.     public void run() { 
  7.         synchronized (this) { 
  8.             System.out.println(new Date() + " 開始休眠" + Thread.currentThread().getName()); 
  9.             count++; 
  10.             try { 
  11.                 Thread.sleep(10000); 
  12.                 System.out.println(new Date() + " 結束休眠" + Thread.currentThread().getName()); 
  13.             } catch (InterruptedException e) { 
  14.                 e.printStackTrace(); 
  15.             } 
  16.         } 
  17.     } 
  18.     // ... 

在上述代碼中鎖定了當前對象,而當前對象在這個示例中是同一個SyncTest的對象。

再次執行main方法,打印日志如下:

  1. Fri Jul 23 23:13:55 CST 2021 開始休眠Thread-0 
  2. Fri Jul 23 23:14:05 CST 2021 結束休眠Thread-0 
  3. Fri Jul 23 23:14:05 CST 2021 開始休眠Thread-1 
  4. Fri Jul 23 23:14:15 CST 2021 結束休眠Thread-1 

可以看到,第一個線程完全執行完畢之后,第二個線程才進行執行,達到預期的同步處理目標。

上面鎖定當前對象還是有一個小缺點,大家在使用時需要注意:比如該類有其他方法也使用了synchronized (this),那么由于兩個方法鎖定的都是當前對象,其他方法也會進行阻塞。所以通常情況下,建議每個方法鎖定各自定義的對象。

比如,單獨定義一個private的變量,然后進行鎖定:

  1. public class SyncTest implements Runnable { 
  2.  
  3.     private Integer count = 0; 
  4.  
  5.     private final Object locker = new Object(); 
  6.  
  7.     @Override 
  8.     public void run() { 
  9.         synchronized (locker) { 
  10.             System.out.println(new Date() + " 開始休眠" + Thread.currentThread().getName()); 
  11.             count++; 
  12.             try { 
  13.                 Thread.sleep(10000); 
  14.                 System.out.println(new Date() + " 結束休眠" + Thread.currentThread().getName()); 
  15.             } catch (InterruptedException e) { 
  16.                 e.printStackTrace(); 
  17.             } 
  18.         } 
  19.     } 

synchronized使用小常識

在使用synchronized時,我們首先要搞清楚它鎖定的是哪個對象,這能幫助我們設計更安全的多線程程式。

在使用和設計鎖時,我們還要了解一下知識點:

  • 對象建議定義為private的,然后通過getter方法訪問。而不是定義為public/protected,否則外界能夠繞過同步方法的控制而直接取得對象并改變它。這也是JavaBean的標準實現方式之一。
  • 當鎖定對象為數組或ArrayList等類型時,getter方法獲得的對象仍可以被改變,這時就需要將get方法也加上synchronized同步,并且只返回這個private對象的clone()。這樣,調用端得到的就是對象副本的引用了。
  • 無論synchronized關鍵字加在方法上還是對象上,取得的鎖都是對象,而不是把一段代碼或函數當作鎖。同步方法很可能還會被其他線程的對象訪問;
  • 每個對象只有一個鎖(lock)和之相關聯;
  • 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制;

小結

通過本文的實踐案例主要為大家輸出兩個關鍵點:第一,不要忽視IDE對代碼的提示信息,某些提示真的很有用,如果深挖還能發現很多性能問題或代碼bug;第二,對于多線程的運用,不僅要全面了解相關的基礎知識點,還需要盡可能的進行壓測,這樣才能讓問題事先暴露出來。

 

責任編輯:武曉燕 來源: 程序新視界
相關推薦

2012-02-21 14:14:47

Java

2022-04-11 10:56:43

線程安全

2024-09-17 17:50:28

線程線程安全代碼

2023-10-27 13:31:18

線程安全多線程

2016-11-23 15:48:05

iOS APPCache

2011-03-29 10:41:51

Java線程安全

2017-02-28 09:37:02

移動支付無現金時代

2019-04-04 11:55:59

2012-11-20 10:47:16

2009-05-30 09:36:18

2021-09-24 10:11:36

Chrome安全漏洞谷歌

2011-04-07 10:47:35

2022-04-06 07:50:28

線程安全代碼

2013-09-05 09:42:06

2009-11-03 13:46:56

Oracle密碼

2011-11-17 10:34:14

內網安全

2011-03-21 10:23:06

2012-12-11 11:28:20

2011-05-20 11:59:32

2013-04-02 11:07:16

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲天堂精品久久 | 久久久精品一区二区三区四季av | 久久精品国产一区二区电影 | 午夜免费观看体验区 | 人人干在线视频 | 免费网站国产 | 亚洲h在线观看 | 成人一区av | 99国产精品久久久 | 请别相信他免费喜剧电影在线观看 | 九九精品在线 | 成人在线视频网址 | 九九热精品视频 | 国产精品久久久久久久久免费樱桃 | 日韩成人精品一区二区三区 | 亚洲精品视频在线 | 久久免费视频1 | 欧美激情欧美激情在线五月 | 欧美一区二区在线观看 | 97综合在线| 日韩一级精品视频在线观看 | 91精品国产一区二区三区 | 久久ww| 久久精品99久久 | 国产视频不卡一区 | 国产美女在线精品免费 | 欧美久久精品一级黑人c片 91免费在线视频 | 亚洲人成人一区二区在线观看 | 成人不卡视频 | 日本天天色 | 午夜性色a√在线视频观看9 | 欧美a级网站 | 久久久久久久亚洲精品 | 亚洲 欧美 激情 另类 校园 | 国产精品久久久久久久久久久久久久 | 五月网婷婷 | 午夜电影网 | 黄视频网站免费观看 | 精品久久国产 | 久久久久国产精品午夜一区 | 国产精品不卡视频 |