一、概念
二、synchronized 同步方法
1、非線程安全的問題存在於實例變量中,如果變量是方法內部的私有變量,則不存在"非線程安全"的問題,永遠是線程安全的,這是方法內部的變量是私有的特性造成的。
2、如果訪問的是類的實例變量,並且方法沒有加synchronized,則會造成多個線程誤修改了同一個變量值,導致線程不安全的問題,這個問題上一篇博文已經提到過了。
3、調用關鍵字synchronized聲明的方法一定是排隊運行的。另外需要牢牢記住“共享”這兩個字,只有共享資源的讀寫訪問才需要同步化,如果不是共享資源,那么根本沒有同步的需要。也就是說,如果不同的線程,訪問的都不是同一個實例變量,那么連線程對資源的爭搶都不存在,哪里來的線程不安全的問題呢?所以也沒有必要進行同步了。
4、synchronized 方法的鎖 為這個類實例對象所持有,也就是說,一個Object對象中的不同synchronized方法 實際上持有的同一把鎖,同屬於Object的實例:

8、同步不具有繼承性,也就是說當子類繼承父類的synchronized方法時,子類的方法是不具有同步性、不是線程安全的,synchronized關鍵字不能繼承,如果子類的方法需要同步性,則需要手動加上synchronized關鍵字。
三、synchronized同步語句塊
1、synchronized 同步方法存在一定的弊端,synchronized 同步方法中 沒有對實例變量操作的那部分代碼也需要進行線程等待,也帶有鎖機制。可是對程序來說,那部分代碼完成可以異步執行,減少等待時間,提高運行效率,這樣就有了synchronized同步語句塊。
2、當兩個並發線程訪問同一個對象Object 中的synchronized(this)同步代碼塊時,一段時間只能有一個線程被執行,另一個線程必須等待當前線程執行完這個代碼塊后才執行該代碼塊。
3、和synchronized方法一樣,synchronized(this)代碼塊也是鎖定當前對象的。當然,Java還支持對“任意對象” 作為鎖對象 來實現同步的功能。這個“任意對象”大多數是實例變量 及方法的參數,使用格式為synchronized(非 this 對象)。
- 多個線程的鎖對象 為同一個 非this 對象時,同一時間只有一個線程可以執行synchronized(非 this 對象)同步塊中的代碼。
- 多個線程的鎖對象 不為同一個 非this 對象時,synchronized(非 this 對象)中的代碼是可以異步執行的。
鎖非this對象的優點:如果在一個類中有很多個synchronized方法,這時雖然能實現同步,但會受到阻塞,所以影響運行效率;但如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序和同步方法是異步的,不與其它鎖this對象同步方法爭搶this鎖,則可以大大提高運行效率。
4、判斷多線程是同步還是異步執行synchronized 的依據就是:(只要對象沒變,即使對象的屬性被改變,運行的結果還是同步的。)
- 多線程如果持有相同的鎖對象,則這些線程之間就是同步的。
- 多線程如果分別獲得鎖對象,則這些線程之間就是異步的。
5、關鍵字synchronized 還可以應用在static靜態方法上,這樣就是對當前的*.java文件對應的Class類進行持鎖。這個可以參考我的這篇博客。
6、要特別注意String常量池緩存的功能,因為可能兩個String對象引用的是同一段內存空間。因此在大多數情況下,同步synchronized代碼塊都不使用String作為鎖對象,而改用其他,比如new Object() 實例化一個Object對象,但他不放入緩存中。
7、程序中應避免出現死鎖,死鎖出現的原因是因為存在鎖之間的交叉引用,兩個線程都在等待對方釋放鎖:
四、volatile 關鍵字
1、多線程中存在私有堆棧中的值 和 公共堆棧中的值不同步的問題。什么意思呢?可能線程在一個地方修改了內存中變量的值,而其它地方線程卻從私有堆棧中去讀取不一致的變量值。
2、關鍵字 volatile 可以用來修飾字段(成員變量),就是告知程序任何對該變量的訪問均需要從主內存中獲取,而對它的改變必須同步刷新回主內存,它能保證所有線程對變量訪問的可見性。
volatile private boolean running=false; //這樣定義一個變量后,強調多線程running的讀取是直接從內存讀
3、synchronized 和 volatile的區別?
- 關鍵字volatile 是線程同步的輕量級實現,所以volatile性能肯定比synchronized要好,並且volatile只能用於修飾變量,而synchronized 可以修飾方法以及代碼塊。
- 多線程訪問volatile不會發生阻塞,而synchronized會出現阻塞。
- volatile 能保證數據的可見性、有序性,但不能保證原子性;而 synchronized 可以保證原子性,也可以間接保證可見性,因為它會把私有內存和公有內存中的數據做同步。(原子性:原子操作是不可分割的整體,沒有其他線程能夠中斷或檢查正在原子操作中的變量,可以在沒有鎖的情況下保證安全)
- 總之,關鍵字 volatile 解決的是變量在多個線程之間的可見性;而 synchronized 關鍵字解決的是多個線程之間訪問資源的同步性。
4、關鍵字volatile 出現非線程安全的原因:
- read 和 load 階段:從主存復制變量到當前線程工作內存。
- use 和 assign 階段:執行代碼,改變共享變量值。
- store 和 write 階段:用工作內存數據刷新主存對應變量的值。
在多線程環境中,use和assign 是多次出現的,但這一操作並不是原子性,也就是說在read和load之后,如果主內存count變量發生修改之后,線程工作內存中的值由於已經加載,不會產生對應的變化,也就是私有內存和公有內存的變量不同步,所以計算出來的結果和預期不一樣,也就出現了非線程安全的問題。
對於用volatile修飾的變量,JVM虛擬機只是保證從主內存加載到線程工作內存的值是最新的,例如線程 1 和線程 2 在進行read 和load 的操作中,發現主內存中count的值都是5,name就會加載這個最新的值,也就是說,volatile關鍵字解決的是變量讀時的可見性問題,但無法保證原子性,對於多個線程訪問同一個實例變量還是需要加鎖同步。
5、除了使用synchronized關鍵字外,還可以使用 AtomicInteget 原子類實現同步。但是在具有邏輯性的情況在,原子類也並不完全 安全,原因在於雖然原子類的方法是原子的,但是方法和方法之間的調用卻不是原子的(這個時候仍然需要synchronized進行同步)。
public class AddCountThread extends Thread { private AtomicInteger count =new AtomicInteger(0); @Override public void run() { super.run(); for (int i=0;i<10000;i++){ System.out.println(count.incrementAndGet()); } } }
6、關鍵字synchronized 不僅可以使多個線程訪問同一個資源具有同步性,而且他還具有將線程工作內存中的私有變量和公共內存的變量進行同步的功能。它包含兩個特征:互斥性和可見性。synchronized 本質上是對一個對象的監視器(monitor)進行獲取,而這個獲取過程是排他的,也就是同一時刻只能有一個線程獲取到由 synchronized 所保護對象的監視器。(任何對象都有自己的監視器)
7、根據 Java 內存模型的 happen before原則,對 volatile 字段的寫入操作先於讀操作,即使兩個線程同時修改和獲取volatile變量,get操作也能拿到最新的值。
8、學習多線程並發,要着重“外練互斥,內修可見”。