多線程原理之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對象标志位,存儲了鎖的資訊,比如鎖的标志位,鎖的類别等。
