目錄
1. synchronized的特性
2. synchronized的使用
3. Java標準庫中的線程安全類
1. synchronized的特性
(1)互斥:
前文已經介紹,某個線程執行到某個對象的synchronized中時,其他線程如果也執行到同一個對象,synchronized就會阻塞等待,進入synchronized修飾的代碼塊相當于加鎖,退出synchronized修飾的代碼塊相當于解鎖;
(2)刷新內存:
前文介紹內存可見性時已經提到:synchronized的工作過程是:
① 獲得互斥鎖,② 從內存拷貝變量的最新內存到寄存器,③ 執行代碼,
④ 將更改后的共享變量的值刷新到寄存器,⑤ 釋放互斥鎖;
故而synchronized也可以保證內存的可見性;
(3)可重入:
同一個線程針對同一個鎖連續加兩次,如果出現了死鎖就是不可重入,不會死鎖就是可重入;
(3.1)死鎖:
class Counter{public int count=0;synchronized public void increase(){synchronized (this){count++;}}
}
外層先加了一次鎖,內層又對同一對象再次加鎖,此時由于外層鎖需要執行完內部代碼才能解鎖,而內層鎖需要等待已經先鎖的外層鎖解鎖后才能執行,此時就會形成死鎖;
(3.2)可重入鎖:
為了解決這個問題,JVM內部將synchronized實現為可重入鎖。
可重入鎖會記錄當前占用鎖的線程以及加鎖次數,線程a第一次加鎖成功后,鎖內部就會記錄當前占用鎖的線程為a,同時加鎖次數為1,后續線程a再加鎖時,進行的加鎖操作就非真實的加鎖操作而是一個偽加鎖,是沒有實質影響的,只是將加鎖次數增加為2;
代碼執行完畢解鎖時,會將計數-1,當鎖的計數減到0時,才會真的解鎖;
可重入鎖降低了程序員的編寫負擔,降低了使用成本,提高了開發效率,但同時由于需要維護鎖所屬的線程以及加減計數會降低運行效率,程序的開銷也會更大;
(3.3)死鎖的必要條件:
① 互斥使用:一個鎖被一個線程占用后,其他線程就無法占用;
② 不可搶占:一個鎖被一個線程占用后,其他線程不能搶占該鎖;
③ 請求與保持:當一個線程占據了多把鎖之后除非顯式釋放鎖,否則這些鎖始終被該線程持有;
(以上三條都是鎖本身的特點)
④ 環路等待:等待關系成環;
在實際開發中需要避免死鎖,關鍵還是從避免環路等待入手:
針對多把鎖加鎖時約定好固定的順序,就可以避免等待關系成環;
但實際情況中很少出現一個線程套鎖的問題;
2. synchronized的使用
使用synchronized的本質是修改了Object對象中“對象頭”內的一個標記;
(1)直接修飾普通方法:此時鎖對象是this:
class Counter{public int count=0;synchronized public void increase(){count++;}
}
?當兩個線程同時對同一個對象進行加鎖的時候才存在競爭;
(2)修飾一個代碼塊:需要顯式指定鎖對象:
class Counter{public int count=0;public void increase(){synchronized (this){count++;}}}
public class Demo1 {private static Counter counter = new Counter();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for(int i=0;i<50000;i++){counter.increase();}});Thread t2 = new Thread(()->{for(int i=0;i<50000;i++){counter.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.count);}
}
counter.increase()表示針對this對象進行加鎖操作;
注:任何對象都可以進行加鎖是java語言的特色;
(3)修飾一個靜態方法:
靜態方法其實就是類方法,普通方法就是實例方法;故而
synchronized修飾一個靜態方法就相當于針對當前類的類對象加鎖;
class Counter{synchronized public static void func1(){}public static void fun2(){synchronized (Counter.class){}}
}
如在上文代碼中,靜態方法func1是表示為synchronized修飾的靜態方法,其效果等效于靜態方法func2;
注:(1)類對象就是運行程序時.class文件被加載到JVM內存中時的形態;
(2)使用synchronized很容易造成線程阻塞,一旦線程阻塞,此時放棄CPU,再次回到CPU的時間就不可控了,一旦代碼中使用了synchronized,則“高性能”幾乎無法實現;
3. Java標準庫中的線程安全類
Java標準庫中已經實現的類中有些是線程安全的,有些是線程不安全的:
線程不安全類:
①ArrayList ②LinkedList ③HashMap ④TreeMap ⑤HashSet ⑥TreeSet ⑦StringBuilder
線程安全類:
①Vector(不推薦) ②HashTable(不推薦)③ConcurrentHashMap ④StringBuffer ⑤String
注:(1)線程安全類由于一些關鍵方法都被synchronized修飾,保證了多線程環境下修改同一個對象不會出現線程不安全問題;
(2)String是線程安全類不是因為synchronized修飾,而是因為String是不可變對象,不存在多線程中修改造成的線程不安全問題;
同時請注意不可變對象與常量以及final沒有必然聯系:
不可變對象是指在該類中沒有提供public的修改屬性的方法,final修飾類表示類不可繼承;