一、可見性
public class SynctestApplication {
//底層使用了lock指令實作鎖快取行
//-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp
private volatile boolean flag = true;
public void refresh() {
flag = false;
System.out.println(Thread.currentThread().getName() + "fix flag");
}
public void load() {
System.out.println(Thread.currentThread().getName() + "start------");
int i = 0;
while (flag) {
i++;
/**
* 1、第一種情況,當執行緒B睡了兩秒鐘,修改了flag的值,但是A執行緒還在回圈,并沒有退出while回圈 ---- 不能
*/
//什么也不做
/**
* 2、睡一秒,跳出了while回圈 ------ 能
* 不釋放鎖,讓出cpu時間片,有執行緒背景關系的切換
*/
/*try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
/**
* 3、println,也可以跳出while回圈 -------- 能
* synchronized 可見性保證 記憶體屏障
*/
//System.out.println("-----------");
/**
* 4、shortWait()休眠納秒
*/
//休眠1秒 ----- 能
//shortWait(1000000); 快取是否失效,執行緒堆疊中的快取有個過期時間
//休眠0.1秒 ---- 不能
//shortWait(1000);
}
System.out.println(Thread.currentThread().getName() + "jump for: i= " + i);
}
public static void main(String[] args) {
SynctestApplication test = new SynctestApplication();
new Thread(() -> test.load(), "threadA").start();
try {
Thread.sleep(2000);
new Thread(() -> test.refresh(), "threadB").start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//1毫秒=1000微秒 1毫秒=1000 000納秒
public static void shortWait(long interval) {
//nanoTime()回傳的是納秒
long start = System.nanoTime();
long end;
do {
end = System.nanoTime();
} while(start + interval >= end);
}
}

上圖中是硬體架構,其實JMM記憶體模型和這個模型是一樣的,只不過JMM操作的各個邏輯塊,其實底層都是和上面的各個硬體有映射關系,
volatile的可見性其實就是利用了快取一致協議(鎖快取行)(MESI)(M:修改 <----E:獨占 <---- S:共享 <---- I:失效),多個執行緒共享一個變數的時候,會將共享變數快取到自己的快取堆疊中,當當前這個變數是被valatile修飾的時候,那個這個變數所在的快取行,就會被上面的MESI四種狀態所標記,當前就會保證只要有一個執行緒修改了共享變數,總線利用總線嗅探會監測到當前的變化,并把其它快取中的該變數的狀態修改為I無效,其它執行緒如果需要獲取該變數,都需要重新從主存中讀取資料,
修改當前快取中的共享變數對于當前執行緒來說是執行緒安全的,但是對被volatile修飾的變數來說,多個執行緒共同操作這個變數的時候,這個程序就不是執行緒安全的,所以volatile并不是執行緒安全的,下面是一個例子
private static volatile long sum = 0;
public static void main(String[] args) {
for (int i=0; i<5; ++i) {
new Thread(()->{
for (int j=0; j<1000; ++j) {
sum++;
}
}).start();
}
System.out.println(sum);//sum每次輸出來的值,都不是我想要的
}
注意:如果多個核的執行緒在操作同一個快取行中的不同變數資料,那么就會出現頻繁的快取失效,即使在代碼層面看這兩個執行緒操作的資料之間完全沒有關系,這種不合理的資源競爭情況就是偽共享
public class FalseSharing {
public static void main(String[] args) {
try {
testPointer(new Pointer());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void testPointer(Pointer pointer) throws InterruptedException {
long start = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.x++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
pointer.y++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(System.currentTimeMillis() - start);
}
}
class Pointer {
/**
* 偽共享,快取行一般是占用64個位元組,下面的x和y總共占用的是8+8=16個位元組,當兩個執行緒分別對x和y進行修改時,會造成當前快取行頻繁失效,得不停地從主存中讀取資料
* 這樣就會對性能有所損耗
*
* 改進策略,浪費空間換取時間的策略,補齊64位元組,讓x和y分別在兩個快取行,這樣在鎖快取行的時候,就不會出現頻繁快取行失效的情況
*/
public volatile long x;
public long a,b,c,d,e,f,g;//避免偽共享,加上x剛好是64位元組,沒加這一行,執行時間大概是3秒,加了以后,執行時間是0.5毫秒
public volatile long y;
}
二、有序性(實作了相當于記憶體屏障的功能)
public class ReOrder {
//保證有序性,禁止重排序
private volatile static ReOrder myInstance;
public static ReOrder getMyInstance() {
if (myInstance == null) {
synchronized (ReOrder.class) {
if (myInstance == null) {
/**
* 單執行緒下,如果把2和3調換順序,不會出現什么問題
* 1、分配記憶體空間
* 2、物件進行初始化
* 3、將地址賦值給myInstance
*
*
* 多執行緒的情況下
* 1、分配記憶體空間
* 3、將地址賦值給myInstance
* 2、物件進行初始化
* 當執行完第二步的時候,myInstance已經有值了,此時,如果第二個執行緒進入到getmyInstance方法里面,第一個判斷結果就會回傳false,
* 直接將myInstance的值回傳,那么此時,就有可能對一個未初始化的物件做操作,那么加上volatile就可以防止,指令排序,2和3的位置也不會被調換
*/
myInstance = new ReOrder();
}
}
}
return myInstance;
}
public static void main(String[] args) {
ReOrder.getMyInstance();
}
}
為什么會有重排序呢???

從上圖中可以看出來(1)(2)執行的結果都一樣,但是對應的資料加載的指令卻不一樣,一個load代表從記憶體中加載一次資料通過高速快取到cpu的程序,如果有重排序,那么就會減少一次這樣的程序,這樣的性能就比較高
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/92412.html
標籤:其他
上一篇:BAT檔案無法匯出CSV
