多線程安全(synchronized、三大特性、Java內存模型)

線程安全問題?安全

什麼是線程安全問題?簡單的說,當多個線程在共享同一個變量,作讀寫的時候,會因爲其餘線程的干擾,致使數據偏差,就會出現線程安全問題。多線程

好比說,多個窗口同時賣票這個案例:併發

複製代碼
 1 public class ThreadTrain2 implements Runnable {
 2     private int tickets = 50;
 3     @Override
 4     public void run() {
 5         while(tickets > 0){
 6                 if (tickets > 0) {                            
              System.out.println(Thread.currentThread().getName() + "賣了第" + (50 - tickets + 1) + "張票");
 7                     tickets--;
 8                 }
 9             }
10         }        
11     }
12     public static void main(String[] args) {
13         ThreadTrain2 tt = new ThreadTrain2();
14         Thread th1 = new Thread(tt, "1號窗口");
15         Thread th2 = new Thread(tt, "2號窗口");
16         th1.start();
17         th2.start();    
18     }
19 }
複製代碼

 

模擬兩個窗口共同賣50張票,什麼都不考慮,按照上面的寫法,運行的結果有時候並非咱們想要的,會徹底亂了套。ide

咱們該如何解決多線程安全問題?

使用多線程同步(synchronized)或者加鎖lock函數

什麼是多線程同步?就是當多個線程共享同一個資源時,不會受到其餘線程的干擾。this

爲何這兩種方法能夠解決線程的安全問題?spa

當把可能發生衝突的代碼包裹在synchronized或者lock裏面後,同一時刻只會有一個線程執行該段代碼,其餘線程必須等該線程執行完畢釋放鎖之後,才能去搶鎖,得到鎖之後,才擁有執行權,這樣就解決的數據的衝突,實現了線程的安全。線程

 賣票的案例同步後爲:對象

複製代碼
 1 public class ThreadTrain2 implements Runnable {
 2     private int tickets = 50;
 3     private static Object obj = new Object();//鎖的對象,能夠是任意的對象
 4     @Override
 5     public void run() {
 6         while(tickets > 0){ 
 7             synchronized (obj) {// 同步代碼塊
 8                 if (tickets > 0) {
 9                     System.out.println(Thread.currentThread().getName() + "賣了第" + (50 - tickets + 1) + "張票");
10                     tickets--;
11                 }
12             }
13         }    
14     }
15     public static void main(String[] args) {
16         ThreadTrain2 tt = new ThreadTrain2();
17         Thread th1 = new Thread(tt, "1號窗口");
18         Thread th2 = new Thread(tt, "2號窗口");
19         th1.start();
20         th2.start();    
21     }
22 }
23
複製代碼

 上面是同步代碼塊的加鎖方式,能夠解決線程安全問題。同時,還有一種同步函數的方式,就是在方法上直接加synchronized,能夠實現一樣的效果,那麼如今有一個問題,在方法上加synchronized修飾,鎖的對象是什麼呢???this。。下面來驗證一下爲何是this:blog

View Code

 點擊+號查看代碼,代碼中的執行結果是絕對正確的,咱們是採用一個線程使用同步代碼塊,另外一個線程使用同步函數的方式,看是否會發生數據錯誤,做爲對比,下面的代碼中同步代碼塊咱們不使用this,而是使用obj這個對象:

View Code

顯然,這段代碼最後會出現數據衝突的狀況,由於兩個線程拿到的不是同一把鎖,也證實了同步函數鎖的是this。

明白了同步函數的鎖是this,那麼加上static之後,鎖的對象會不會發生改變,仍是依然是this???

先鎖this,驗證是不是this:

View Code

出現了數據錯誤,這裏咱們不作猜想,只作驗證,靜態的同步函數鎖的是當前類的字節碼文件,代碼驗證:

View Code

 多線程死鎖

 同步中嵌套同步,鎖沒有來得及釋放,一直等待,就致使死鎖。

下面這段代碼,多運行幾回就會出現死鎖,思路是開啓兩個線程,讓這兩個線程執行的代碼獲取的鎖的順序不一樣,第一個線程須要先得到obj對象鎖,而後再得到this鎖,才能夠執行代碼,而後釋放兩把鎖。線程2須要先得到this鎖,再獲取obj對象鎖纔可執行代碼,而後釋放兩把鎖。可是,當線程1得到了obj鎖以後,線程2得到了this鎖,這時候線程1須要得到this鎖纔可執行,可是線程2也沒法獲取到obj對象鎖執行代碼並釋放,因此兩個線程都拿着一把鎖不釋放,這就產生了死鎖。

View Code

 多線程的三大特性

原子性

原子性就是在執行一個或者多個操做的過程當中,要麼所有執行完不被任何因素打斷,要麼不執行。好比銀行轉帳,A帳戶減去100元,B帳戶必須增長100元,對這兩個帳戶的操做必須保證原子性,纔不會出現問題。還有好比:i=i+1的操做,須要先取出i,而後對i進行+1操做,而後再給i賦值,這個式子就不是原子性的,須要同步來實現數據的安全。

原子性就是爲了保證數據一致,線程安全。

可見性

當多個線程訪問同一個變量時,一個線程修改了變量的值,其餘的線程能當即看到,這就是可見性。

這裏講一下Java內存模型?簡稱JMM,決定了一個線程與另外一個線程是否可見,包括主內存(存放共享的全局變量)和私有本地內存(存放本地線程私有變量)

本地私有內存存放的是共享變量的副本,線程操做共享變量,首先操做的是本身本地內存的副本,當同一時刻只有一個線程操做共享變量時,該線程操做完畢本地內存,而後會刷新到主內存,而後主內存會通知另外一個線程,進而更新;可是若是同一時刻有多個線程操做共享變量,會來不及更新主內存進而通知其餘線程更新變量,就會出現衝突問題。

有序性

就是程序的執行順序會按照代碼前後順序進行執行,通常狀況下,處理器因爲要提升執行效率,對代碼進行重排序,運行的順序可能和代碼前後順序不一樣,可是結果同樣。單線程下不會出現問題,多線程就會出現問題了。

volatile

保證可見性,可是不保證原子性。

下面這個案例10個線程共享同一個count,進行+1操做:

複製代碼
public class VolatileTest extends Thread{
    private volatile static int count = 0;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println(Thread.currentThread().getName()+":"+count);
    }
    public static void main(String[] args) {
        VolatileTest[] list = new VolatileTest[10];
        for (int i = 0; i < list.length; i++) {
            list[i] = new VolatileTest();
        }
        for (int i = 0; i < list.length; i++) {
            list[i].start();
        }
    }
}
複製代碼

 多運行幾回,就會出現最後結果有不到1000的狀況,也就證實了volatile不會保證原子性。

保證原子性,jdk1.5以後,併發包提供了不少原子類,例如AtomicInteger :

複製代碼
public class VolatileTest2 extends Thread{
    private static AtomicInteger count = new AtomicInteger();
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            count.incrementAndGet();
        }
        System.out.println(Thread.currentThread().getName()+":"+count.get());
    }
    public static void main(String[] args) {
        VolatileTest2[] list = new VolatileTest2[10];
        for (int i = 0; i < list.length; i++) {
            list[i] = new VolatileTest2();
        }
        for (int i = 0; i < list.length; i++) {
            list[i].start();
        }
    }
}
複製代碼
AtomicInteger解決了同步, 最後的結果最大的確定是1000
相關文章
相關標籤/搜索