歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java多線程之多線程的鎖機制

    當兩條線程同時訪問一個類的時候,可能會帶來一些問題。並發線程重入可能會帶來內存洩漏、程序不可控等等。不管是線程間的通訊還是線程共享數據都需要使用Java的鎖機制控制並發代碼產生的問題。本篇總結主要著名Java的鎖機制,闡述多線程下如何使用鎖機制進行並發線程溝通。

1、並發下的程序異常

  先看下下面兩個代碼,查看異常內容。

  異常1:單例模式

 
 1 package com.scl.thread;
 2 
 3 public class SingletonException
 4 {
 5     public static void main(String[] args)
 6     {
 7         // 開啟十條線程進行分別測試輸出類的hashCode,測試是否申請到同一個類
 8         for (int i = 0; i < 10; i++)
 9         {
10             new Thread(new Runnable()
11             {
12                 @Override
13                 public void run()
14                 {
15                     try
16                     {
17                         Thread.sleep(100); 
18                     }
19                     catch (InterruptedException e)
20                     {
21                         e.printStackTrace();
22                     }
23                     System.out.println(Thread.currentThread().getName() + "  " + MySingle.getInstance().hashCode());
24                 }
25             }).start();
26         }
27     }
28 }
29 
30 class MySingle
31 {
32     private static MySingle mySingle = null;
33 
34     private MySingle()
35     {
36     }
37 
38     public static MySingle getInstance()
39     {
40         if (mySingle == null) { mySingle = new MySingle(); }
41         return mySingle;
42     }
43 }
view code

    運行結果如下:

     

  由上述可見,Thread-7與其他結果不一致,證明了在多線程並發的情況下這種單例寫法存在問題,問題就在第40行。多個線程同時進入了空值判斷,線程創建了新的類。

  異常2:線程重入,引發程序錯誤

     現在想模擬國企生產規則,每個月生產100件產品,然後當月消費20件,依次更替。模擬該工廠全年的生產與銷售

      備注:舉這個實例是為後面的信號量和生產者消費者問題做鋪墊。可以另外舉例,如開辟十條線程,每條線程內的任務就是進行1-10的累加,每條線程輸出的結果不一定是55(線程重入導致)

 
 1 package com.scl.thread;
 2 
 3 //每次生產100件產品,每次消費20件產品,生產消費更替12輪
 4 public class ThreadCommunicateCopy
 5 {
 6     public static void main(String[] args)
 7     {
 8         final FactoryCopy factory = new FactoryCopy();
 9         new Thread(new Runnable()
10         {
11 
12             @Override
13             public void run()
14             {
15                 try
16                 {
17                     Thread.sleep(2000);
18                 }
19                 catch (InterruptedException e)
20                 {
21                     e.printStackTrace();
22                 }
23 
24                 for (int i = 1; i <= 12; i++)
25                 {
26                     factory.createProduct(i);
27                 }
28 
29             }
30         }).start();
31 
32         new Thread(new Runnable()
33         {
34 
35             @Override
36             public void run()
37             {
38                 try
39                 {
40                     Thread.sleep(2000);
41                 }
42                 catch (InterruptedException e)
43                 {
44                     e.printStackTrace();
45                 }
46 
47                 for (int i = 1; i <= 12; i++)
48                 {
49                     factory.sellProduct(i);
50                 }
51 
52             }
53         }).start();
54 
55     }
56 }
57 
58 class FactoryCopy
59 {
60     //生產產品
61     public void createProduct(int i)
62     {
63 
64         for (int j = 1; j <= 100; j++)
65         {
66             System.out.println("第" + i + "輪生產,產出" + j + "件");
67         }
68     }
69     //銷售產品
70     public void sellProduct(int i)
71     {
72         for (int j = 1; j <= 20; j++)
73         {
74             System.out.println("第" + i + "輪銷售,銷售" + j + "件");
75         }
76 
77     }
78 }
View Code

  結果如下:

   

   該結果不能把銷售線程和生產線程的代碼分隔開,如果需要分隔開。可以使用Java的鎖機制。下面總結下如何處理以上兩個問題。

 

2、使用多線程編程目的及一些Java多線程的基本知識

  使用多線程無非是期望程序能夠更快地完成任務,這樣並發編程就必須完成兩件事情:線程同步及線程通信。

      線程同步指的是:控制不同線程發生的先後順序。

      線程通信指的是:不同線程之間如何共享數據。 

   Java線程的內存模型:每個線程擁有自己的棧,堆內存共享 [來源:Java並發編程藝術 ],如下圖所示。 鎖是線程間內存和信息溝通的載體,了解線程間通信會對線程鎖有個比較深入的了解。後面也會詳細總結Java是如何根據鎖的信息進行兩條線程之間的通信。

         

2、使用Java的鎖機制

    Java語音設計和數據庫一樣,同樣存在著代碼鎖.實現Java代碼鎖比較簡單,一般使用兩個關鍵字對代碼進行線程鎖定。最常用的就是volatile和synchronized兩個

     2.1 synchronized

       synchronized關鍵字修飾的代碼相當於數據庫上的互斥鎖。確保多個線程在同一時刻只能由一個線程處於方法或同步塊中,確保線程對變量訪問的可見和排它,獲得鎖的對象在代碼結束後,會對鎖進行釋放。

       synchronzied使用方法有兩個:①加在方法上面鎖定方法,②定義synchronized塊。

        模擬生產銷售循環,可以通過synchronized關鍵字控制線程同步。代碼如下:     

 
  1 package com.scl.thread;
  2 
  3 //每次生產100件產品,每次消費20件產品,生產消費更替10輪
  4 public class ThreadCommunicate
  5 {
  6     public static void main(String[] args)
  7     {
  8         final FactoryCopy factory = new FactoryCopy();
  9         new Thread(new Runnable()
 10         {
 11 
 12             @Override
 13             public void run()
 14             {
 15                 try
 16                 {
 17                     Thread.sleep(2000);
 18                 }
 19                 catch (InterruptedException e)
 20                 {
 21                     e.printStackTrace();
 22                 }
 23 
 24                 for (int i = 1; i <= 12; i++)
 25                 {
 26                     factory.createProduct(i);
 27                 }
 28 
 29             }
 30         }).start();
 31 
 32         new Thread(new Runnable()
 33         {
 34 
 35             @Override
 36             public void run()
 37             {
 38                 try
 39                 {
 40                     Thread.sleep(2000);
 41                 }
 42                 catch (InterruptedException e)
 43                 {
 44                     e.printStackTrace();
 45                 }
 46 
 47                 for (int i = 1; i <= 12; i++)
 48                 {
 49                     factory.sellProduct(i);
 50                 }
 51 
 52             }
 53         }).start();
 54 
 55     }
 56 }
 57 
 58 class Factory
 59 {
 60     private boolean isCreate = true;
 61 
 62     public synchronized void createProduct(int i)
 63     {
 64         while (!isCreate)
 65         {
 66             try
 67             {
 68                 this.wait();
 69             }
 70             catch (InterruptedException e)
 71             {
 72                 e.printStackTrace();
 73             }
 74         }
 75 
 76         for (int j = 1; j <= 100; j++)
 77         {
 78             System.out.println("第" + i + "輪生產,產出" + j + "件");
 79         }
 80         isCreate = false;
 81         this.notify();
 82     }
 83 
 84     public synchronized void sellProduct(int i)
 85     {
 86         while (isCreate)
 87         {
 88             try
 89             {
 90                 this.wait();
 91             }
 92             catch (InterruptedException e)
 93             {
 94                 e.printStackTrace();
 95             }
 96         }
 97         for (int j = 1; j <= 20; j++)
 98         {
 99             System.out.println("第" + i + "輪銷售,銷售" + j + "件");
100         }
101         isCreate = true;
102         this.notify();
103     }
104 }
View Code

   上述代碼通過synchronized關鍵字控制生產及銷售方法每次只能1條線程進入。代碼中使用了isCreate標志位控制生產及銷售的順序。

       備注:默認的使用synchronized修飾方法, 關鍵字會以當前實例對象作為鎖對象,對線程進行鎖定。

                 單例模式的修改可以在getInstance方式中添加synchronized關鍵字進行約束,即可。

                wait方法和notify方法將在第三篇線程總結中講解。

    2.2 volatile

  volatile關鍵字主要用來修飾變量,關鍵字不像synchronized一樣,能夠塊狀地對代碼進行鎖定。該關鍵字可以看做對修飾的變量進行了讀或寫的同步操作。

  如以下代碼:

 
 1 package com.scl.thread;
 2 
 3 public class NumberRange
 4 {
 5     private volatile int unSafeNum;
 6 
 7     public int getUnSafeNum()
 8     {
 9         return unSafeNum;
10     }
11 
12     public void setUnSafeNum(int unSafeNum)
13     {
14         this.unSafeNum = unSafeNum;
15     }
16 
17     public int addVersion()
18     {
19         return this.unSafeNum++;
20     }
21 }
View Code

 代碼編譯後功能如下:

 
 1 package com.scl.thread;
 2 
 3 public class NumberRange
 4 {
 5     private volatile int unSafeNum;
 6 
 7     public synchronized int getUnSafeNum()
 8     {
 9         return unSafeNum;
10     }
11 
12     public synchronized void setUnSafeNum(int unSafeNum)
13     {
14         this.unSafeNum = unSafeNum;
15     }
16 
17     public int addVersion()
18     {
19         int temp = getUnSafeNum();
20         temp = temp + 1;
21         setUnSafeNum(temp);
22         return temp;
23     }
24 
25 }
View Code

 由此可見,使用volatile變量進行自增或自減操作的時候,變量進行temp= temp+1這一步時,多條線程同時可能同時操作這一句代碼,導致內容出差。線程代碼內的原子性被破壞了。

以上是對Java鎖機制的總結,如有問題,煩請指出糾正。代碼及例子很大一部分參考了《Java 並發編程藝術》[方騰飛 著] PDF+源碼下載見 http://www.linuxidc.com/Linux/2016-07/133404.htm

Copyright © Linux教程網 All Rights Reserved