天天看點

多線程原理之Synchronized的初步使用線程安全問題的例子解決線程安全的辦法為什麼每個對象都可以當鎖鎖存在哪裡

多線程原理之Synchronized的初步使用

  • 線程安全問題的例子
  • 解決線程安全的辦法
    • 鎖類對象
    • 鎖類執行個體
    • 鎖變量
  • 為什麼每個對象都可以當鎖
  • 鎖存在哪裡
多線程很容易的一個點就是共享變量的使用,容易引發線程安全問題

線程安全問題的例子

public class ThreadSyn {
    private static int count = 0;

    public static void inc(){
        try{
            // 加長一段線程執行的時間,不然count++的速度遠小于循環建立線程的速度,展現不出效果
            Thread.sleep(1);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
        count++;

    }
    
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0;i<1000;i++){
            new Thread(()->ThreadSyn .inc()).start();
        }
        Thread.sleep(3000);
        System.out.println("結果:"+ count);
    }
}
           

預期結果應該是1000,但實際上會小于1000,就是因為對共享變量count的進行中,出現了線程安全問題,比如線程A先建立拿到的count是900,線程B後建立的,因為A還沒執行完count++操作,拿到的也是900,最後的結果肯定會少1.

解決線程安全的辦法

最常見最容易想到的自然是鎖,鎖中最常見的就是Synchronized。而Synchronized有三種粒度的用法

鎖類對象

這是最粗粒度的,給類加鎖,一般是用在靜态方法或者靜态變量上,這樣的話,凡是用到這個類,就要擷取鎖

// 給整個方法加鎖
public synchronized static void incW1(){
        try{
            Thread.sleep(1);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
        count++;
    }
           
public static void incW2(){
	// 給代碼塊加鎖
    synchronized (ThreadSyn.class){
        try{
            Thread.sleep(1);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
        count++;
    }
}
           

鎖類執行個體

這種是鎖類對象,凡是用到這個對象的都需要擷取鎖,但如果是多個執行個體對象,則會有不同的鎖

public synchronized void incW1(){
     try{
         Thread.sleep(1);
     } catch (InterruptedException e){
         e.printStackTrace();
     }
     count++;
}
           
public void incW2(){
   synchronized (this){
        try{
            Thread.sleep(1);
        } catch (InterruptedException e){
            e.printStackTrace();
        }
        count++;
    }
}
           

鎖變量

凡是用到這個變量,就需要擷取鎖,需要差別的隻是這個變量是執行個體變量還是靜态變量,執行個體變量存在于執行個體中,靜态變量存在于類中

為什麼每個對象都可以當鎖

因為java中的對象都繼承自Object,Object在底層的c++語言中都有通用的一部分實作,對應的c++對象(oop)都有一個對象螢幕,這是一個同步的變量,争搶鎖的過程其實就是在修改這個對象螢幕的鎖标志。

如果反編譯同步代碼塊,會發現代碼裡面有一個monitorenter ,表示去獲得一個對象螢幕。monitorexit 表示釋放 monitor 螢幕的所有權。

這個c++對象上的螢幕其實就是鎖的本質

鎖存在哪裡

鎖是存在對象對應的c++對象中,裡面有個一個markworld對象标志位,存儲了鎖的資訊,比如鎖的标志位,鎖的類别等。

多線程原理之Synchronized的初步使用線程安全問題的例子解決線程安全的辦法為什麼每個對象都可以當鎖鎖存在哪裡