內存模型
CPU指令執行速度快,數據保存在主存中,即物理內存,爲避免降低CPU執行速度,將數據從主存中copy到高速緩存中去,直接從高速緩存讀取和寫入數據,執行完畢後再將數據刷回到主存。
當多核,多線程的時候,對內存中同一個變量的調用,放入各自的高速緩存去執行,可能就會造成緩存不一致的問題。通常稱這種被多個線程訪問的變量爲共享變量。
一個變量在多個CPU中都存在緩存(一般在多線程編程時纔會出現),那麼就可能存在緩存不一致的問題。
爲了解決緩存不一致性問題,通常來說有以下2種解決方法:
- 通過在總線加LOCK#鎖的方式
對總線加鎖,阻塞了其他CPU對其他部件的訪問(如主存),從而使得只能有一個CPU能使用這個變量的內存。但會導致其他CPU無法訪問內存,導致效率低下。 - 通過緩存一致性協議
它核心的思想是:當CPU寫數據時,如果發現操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發出信號通知其他CPU將該變量的緩存行置爲 無效狀態,因此當其他CPU需要讀取這個變量時,發現自己緩存中緩存該變量的緩存行是無效的,那麼它就會從內存重新讀取。
併發性概念
原子性
一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
可見性
當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
有序性
程序執行的順序按照代碼的先後順序執行。
指令重排序:處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。
★注:
1、處理器在進行重排序時是會考慮指令之間的數據依賴性,如果一個指令Instruction 2必須用到Instruction 1的結果,那麼處理器會保證Instruction 1會在Instruction 2之前執行。
2、指令重排序不會影響單個線程的執行,但是會影響到多個線程併發執行的正確性。
要想併發程序正確地執行,必須要保證原子性、可見性以及有序性。
內存模型
Java內存模型規定所有的變量都是存在主存當中(類似於前面說的物理內存),每個線程都有自己的工作內存(類似於前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作。並且每個線程不能訪問其他線程的工作內存。
java中對於原子性、可見性、有序性的保證:
java保證原子性
Java內存模型只保證了基本讀取和賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)是原子性操作。
x = 10; //語句1
y = x; //語句2
語句1是直接將數值10賦值給x,也就是說線程執行這個語句的會直接將數值10寫入到工作內存中。
語句2實際上包含2個操作,它先要去讀取x的值,再將x的值寫入工作內存,雖然讀取x的值以及 將x的值寫入工作內存 這2個操作都是原子性操作,但是合起來就不是原子性操作了。
如果要實現更大範圍操作的原子性,可以通過synchronized和Lock來實現
java保證可見性
對於可見性,Java提供了volatile關鍵字來保證可見性。
當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。
通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然後執行同步代碼,並且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。
java保證有序性
在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程併發執行的正確性。
volatile關鍵字來保證一定的“有序性,。另外可以通過synchronized和Lock來保證有序性,
java內存模型:happens-before原則,具備一定先天的有序性,無需任何其他手段就能保證有序性。
程序次序規則:在單線程中,對於不存在數據依賴性的指令會出現指令重排序,但是最終結果和代碼執行順序結果是正確的,因此在單線程中看起來是程序有序執行的,但是不適用多線程併發操作。
深入解析Volatile關鍵字
1、volatile保證了可見性
不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。禁止進行指令重排序
2、volatile不保證原子性
volatile沒辦法保證對變量的操作的原子性。
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保證前面的線程都執行完
Thread.yield();
System.out.println(test.inc);
}
}
3、volatile一定程度保證有序性
volatile關鍵字禁止指令重排序有兩層意思:
1)當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對後面的操作可見;在其後面的操作肯定還沒有進行;
2)在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。
volatile關鍵字使用場景
volatile關鍵字是無法替代synchronized關鍵字的,因爲volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下2個條件:
1)對變量的寫操作不依賴於當前值
2)該變量沒有包含在具有其他變量的不變式中
Java中使用volatile的幾個場景:
1.狀態標記量
volatile boolean inited = false;
//線程1:
context = loadContext();
inited = true;
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
2.double check
/**
單例
*/
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}