線程安全之Synchronized關鍵字

 

1 多線程下爲什麼會存在線程安全問題

線程的合理使用能夠提升程序的處理性能,一是能夠利用多核 CPU 來實現線程的並行執行,二是線程的異步化執行能夠提高系統的吞吐量。

雖然線程有這些優點,但同時也帶來了很多問題。比如說:

1.1 共享變量帶來的安全性問題

先來看個圖:

 

一個變量 i ,如果線程 A 或者線程 B 單獨訪問並且修改變量 i 的值沒有任何問題,那如果並行的修改變量 i ,那就會有安全性問題。

然後用代碼來模擬一下這種場景,爲了更好的看到效果,我用100個線程:


 
public class ThreadDemo1 {      private static int i = 0;      public static void inc() {         try {             Thread.sleep(1);         } catch (InterruptedException e) {             e.printStackTrace();         }         i++;     }      public static void main(String[] args) throws InterruptedException {         for (int i = 0; i < 100; i++) {             new Thread(() -> ThreadDemo1.inc()).start();         }         Thread.sleep(1000);         System.out.println("運行結果" + i);     } } 

輸出結果:

88

這個輸出結果是不固定的,第一次可能是 88 ,第二次可能是 87 ,這個結果就和我們預期的結果不一致(預期結果是100),所以一個對象是否是線程安全的,取決於它是否會被多個線程訪問,以及程序中是如何去使用這個對象的。如果 多個線程訪問同一個共享對象,在不需額外的同步以及調用端代碼不用做其他協調的情況下,這個共享對象的狀態 依然是正確的(正確性意味着這個對象的結果與我們預期 規定的結果保持一致),那說明這個對象是線程安全的。

對於線程安全性,本質上是管理對於數據狀態的訪問,而且這個這個狀態通常是共享的、可變的。共享:是指這個 數據變量可以被多個線程訪問;可變:指這個變量的值在 它的生命週期內是可以改變的。

2.如何保證線程並行的數據安全性-Synchroinzed

針對上面那種情況,我們該如何解決這種問題呢?首先想到的就是加鎖,並且這種鎖必須是互斥的。比如上面的圖片的例子,如果線程A在修改 i 的值時,線程 B 就不能去修改 i 的值。也就是說並行去修改共享變量的值會有線程安全性問題,那麼我們不讓你並行,不就解決了這個問題嘛。所以java提供了 Synchroinzed 關鍵字。

2.1 Synchroinzed 的基本認識

Synchroinzed 很早就有了,只是之前是重量級鎖,所以很好有人使用。在 javaSE 1.6 對Synchroinzed進行了優化引入了偏向鎖和輕量級鎖。所以在併發量不高的情況還是推薦使用 Synchroinzed 來加鎖。爲什麼是併發量不高的情況推薦使用,因爲併發量高的情況 Synchroinzed 會升級爲重量級鎖。

2.2 Synchroinzed 的三種加鎖方式

  1. 修飾實例方法,鎖是當前實例對象 ,進入同步代碼前要獲得當前實例的鎖
  2. 修飾靜態方法,鎖是當前類的class對象 ,進入同步代碼前要獲得當前類對象的鎖
  3. 修飾代碼塊,鎖是括號裏面的對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。
  4. 加q羣:478052716 免費領取(Java架構資料,視頻資料,BATJ面試資料)

看下簡單的代碼


 
public class SynchroinzedDemo {      /**      * 對靜態方法加鎖      */     public static synchronized void test(){}     /**      * 對實例方法加鎖      */     public synchronized void test1(){}     /**      * 對代碼塊加鎖      */     public void test2(){         synchronized(this){}     } } 

然後我們將上面的例子實現 synchronized 加鎖:


 
public class ThreadDemo1 {      private static int i = 0;      public static void inc() {         synchronized (ThreadDemo1.class){             try {                 Thread.sleep(1);             } catch (InterruptedException e) {                 e.printStackTrace();             }             i++;         }     }      public static void main(String[] args) throws InterruptedException {         for (int i = 0; i < 100; i++) {             new Thread(() -> ThreadDemo1.inc()).start();         }         Thread.sleep(1000);         System.out.println("運行結果" + i);     } } 

運行結果:

運行結果100

完美的解決共享變量並行修改帶來的線程安全問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章