高併發系列之四:JMM相關的一些概念

JMM:java內存模型

併發程序比串行程序複雜的多,其中很重要的一個原因就是併發程序中數據訪問的一致性和安全性問題。如何保證在一個線程可以看到正確的數據?這在串行程序中就不是問題,但是在併發程序中,這卻成了最重要的一個問題。
假如:讀取了一個指,a=1,在串行程序中肯定這個a=1,但是如果這個變量是線程共享的數據,那麼在併發程序中我們有可能得到的a!=1,可能等於2、3、4、5…
JMM關鍵的技術點是圍繞着多線程的原子性、可見性、有序性來建立的。

思維導圖

在這裏插入圖片描述

原子性

原子性是指操作是不可分的,要麼全部一起執行,要麼不執行,在java中表現爲,對於共享變量的操作是不可分的,必須連續完成。比如對於a++,我們要執行三個操作:

  1. 讀取變量a的指,假如a=1
  2. a的指也就是1+1,得到指2
  3. 將2的指賦值給變量a,此時a的指應該爲2
    這三個操作中任意一個操作,a的值如果被其他線程篡改了,那麼都會出現我們不希望出現的結果。所以必須保證這3個操作是原子性的,在操作a++的過程中,其他線程不會改變a的值,如果在上面的過程中出現其他線程修改了a的值,在滿足原子性的原則下,上面的操作應該失敗。

java中實現原子操作的方法大致有2種:鎖機制、無鎖CAS機制。

可見性

可見性是指一個線程對共享變量的修改,對於其他的線程來說是否是可見的,可能有些人會說,修改了之後別的線程肯定可以看見呀,線程又不瞎。但是確實會有這個問題,那麼爲什麼會發生這個問題呢,看下具體的線程、工作內存、主內存的交互關係圖如下:
在這裏插入圖片描述
每個線程都有自己的工作內存,工作內存中的數據來自主內存數據的拷貝,線程對變量進行修改,線程結束後纔會將變量的指寫回到主內存中去,當兩個線程都要修改共享變量時,就有可能發生這個問題:
假設線程1先從主內存拷貝了共享變量a,並且修改了共享變量a,但是還未將變量a寫回到主內存,這時,線程2拷貝了主內存的變量a到線程2的工作內存中,之後線程1結束,將共享變量a寫回到主內存,這時就會發生線程2和主內存的變量a的值不一致,問題
不可見:線程將工作內存中的共享變量修改了,還未寫回至主內存,這時其他的線程對於本次修改是不可見的;而其他線程沒有讀取到主內存中的最新值而是使用的舊值的情況也可以列爲不可見的。
共享變量可見性的實現原理

  1. 線程A對共享變量的修改要被線程B看見的話需要進行以下步驟:
  2. 線程A在自己的主內存中將共享變量修改之後,需要將共享變量的值刷新到主內存中,線程B要把主內存中的值更新到自己的工作內存中;

關於線程可見性可以使用volatile、synchronize、鎖來實現

有序性

有序性是指,程序按照代碼的順序執行。
爲了性能優化,編譯器和處理器進行指令重排序,有時會改變程序語句的先後順序,常常被說起的的例子就是單例模式實現的一個例子:

public class Singleton{
	private static Singleton instance;
	private Singleton(){};
	
	static Singleton getInstance(){
		if(instance == null){
			synchronize(Singleton.class){
				if(instance == null){
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

主要是看這個:instance = new Singleton();
未被編譯器優化的執行順序

  1. 分配一塊內存M
  2. 在內存M上初始化對象Singleton
  3. 將M的內存地址賦值給instance變量

編譯器優化後的執行順序

  1. 分配一塊內存M
  2. 將M的內存地址賦值給instance變量
  3. 在內存M上初始化Singleton對象

如何復現這個問題
假如現在2個線程,剛好執行的代碼被編譯器優化過,過程如下:

在這裏插入圖片描述
可以看到,線程B在if判斷得到對象instance不是null,就會返回一個未被初始化的instance,可能會產生一些意想不到的錯誤。

如果需要實現安全的單例模式,可以將變量instance 前面加上volatile關鍵字,volatile可以阻止編譯器指令重排序。
也可以使用靜態內部類的方式實現安全的單例模式:

public class Singleton{
	private Singleton(){}
	private static class SingletonHandle{
		priavte static Singleton instance = new Singleton();
	} 
	public static Singleton getInstance(){
		return SingletonHandle.instance;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章