Volatile
什么是 Volatile
能夠保證線程可見性,當(dāng)一個(gè)線程修改共享變量時(shí),能夠保證對(duì)另外一個(gè)線程可見性,
但是注意他不能夠保證共享變量的原子性問題。
Volatile的特性
可見性
能夠保證線程可見性,當(dāng)一個(gè)線程修改共享變量時(shí),能夠保證對(duì)另外一個(gè)線程可見性,
但是注意他不能夠保證共享變量的原子性問題。
public class Mayikt extends Thread {
/**
* lock 鎖 匯編的指令 強(qiáng)制修改值,立馬刷新主內(nèi)存中 另外線程立馬可見刷新主內(nèi)存數(shù)據(jù)
*/
private static volatile boolean FLAG = true;
@Override
public void run() {
while (FLAG) {
}
}
public static void main(String[] args) throws InterruptedException {
new Mayikt().start();
Thread.sleep(1000);
FLAG = false;
}
}
順序性
程序執(zhí)行程序按照代碼的先后順序執(zhí)行。
原子性
即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過程,要么失敗。
CPU多核硬件架構(gòu)剖析
CPU每次從主內(nèi)存讀取數(shù)據(jù)比較慢,而現(xiàn)代的CPU通常涉及多級(jí)緩存,CPU讀主內(nèi)存
按照空間局部性原則加載 局部快到緩存中。

為什么會(huì)產(chǎn)生可見性的原因
因?yàn)槲覀僀PU讀取主內(nèi)存共享變量的數(shù)據(jù)時(shí)候,效率是非常低,所以對(duì)每個(gè)CPU設(shè)置
對(duì)應(yīng)的高速緩存 L1、L2、L3 緩存我們共享變量主內(nèi)存中的副本。
相當(dāng)于每個(gè)CPU對(duì)應(yīng)共享變量的副本,副本與副本之間可能會(huì)存在一個(gè)數(shù)據(jù)不一致性的問題。
比如線程線程B修改的某個(gè)副本值,線程A的副本可能不可見。導(dǎo)致可見性問題。
JMM內(nèi)存模型
Java內(nèi)存模型定義的是一種抽象的概念,定義屏蔽java程序?qū)Σ煌牟僮飨到y(tǒng)的內(nèi)存訪問差異。
主內(nèi)存
存放我們共享變量的數(shù)據(jù)
工作內(nèi)存
每個(gè)CPU對(duì)共享變量(主內(nèi)存)的副本。堆+方法區(qū)
JMM八大同步規(guī)范

(1)lock(鎖定):作用于 主內(nèi)存的變量,把一個(gè)變量標(biāo)記為一條線程獨(dú)占狀態(tài)
(2)unlock(解鎖):作用于 主內(nèi)存的變量,把一個(gè)處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定
(3)read(讀取):作用于 主內(nèi)存的變量,把一個(gè)變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的 工作內(nèi)存中,以便隨后的load動(dòng)作使用
(4)load(載入):作用于 工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中
(5)use(使用):作用于 工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量值傳遞給執(zhí)行引擎
(6)assign(賦值):作用于 工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量
(7)store(存儲(chǔ)):作用于 工作內(nèi)存的變量,把工作內(nèi)存中的一個(gè)變量的值傳送到 主內(nèi)存中,以便隨后的write的操作
(8)write(寫入):作用于 工作內(nèi)存的變量,它把store操作從工作內(nèi)存中的一個(gè)變量的值傳送到 主內(nèi)存的變量中
Volatile匯編lock指令
將當(dāng)前處理器緩存行數(shù)據(jù)立刻寫入主內(nèi)存中。
寫的操作會(huì)觸發(fā)總線嗅探機(jī)制,同步更新主內(nèi)存的值。
Volatile的底層實(shí)現(xiàn)原理
通過匯編lock前綴指令觸發(fā)底層鎖的機(jī)制
鎖的機(jī)制兩種:總線鎖(老機(jī)器一般都是這個(gè))/MESI緩存一致性協(xié)議
主要幫助我們解決多個(gè)不同cpu之間三級(jí)緩存之間數(shù)據(jù)同步
總線鎖
當(dāng)一個(gè)cpu(線程)訪問到我們主內(nèi)存中的數(shù)據(jù)時(shí)候,往總線總發(fā)出一個(gè)Lock鎖的信號(hào),其他的線程不能夠?qū)υ撝鲀?nèi)存做任何操作,變?yōu)樽枞麪顟B(tài)。該模式,存在非常大的缺陷,就是將并行的程序,變?yōu)榇?,沒有真正發(fā)揮出cpu多核的好處。
MESI協(xié)議
1.M 修改 (Modified) 這行數(shù)據(jù)有效,數(shù)據(jù)被修改了,和主內(nèi)存中的數(shù)據(jù)不一致,數(shù)據(jù)只存在于本Cache中。
2.E 獨(dú)享、互斥 (Exclusive) 這行數(shù)據(jù)有效,數(shù)據(jù)和主內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)只存在于本Cache中。
3.S 共享 (Shared) 這行數(shù)據(jù)有效,數(shù)據(jù)和主內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)存在于很多Cache中。
4.I 無效 (Invalid) 這行數(shù)據(jù)無效。
E:獨(dú)享:當(dāng)只有一個(gè)cpu線程的情況下,cpu副本數(shù)據(jù)與主內(nèi)存數(shù)據(jù)如果
保持一致的情況下,則該cpu狀態(tài)為E狀態(tài) 獨(dú)享。
S:共享:在多個(gè)cpu線程的情況了下,每個(gè)cpu副本之間數(shù)據(jù)如果保持一致
的情況下,則當(dāng)前cpu狀態(tài)為S
M:如果當(dāng)前cpu副本數(shù)據(jù)如果與主內(nèi)存中的數(shù)據(jù)不一致的情況下,則當(dāng)前cpu狀態(tài)
為M
I: 總線嗅探機(jī)制發(fā)現(xiàn) 狀態(tài)為m的情況下,則會(huì)將該cpu改為i狀態(tài) 無效
該cpu緩存主動(dòng)獲取主內(nèi)存的數(shù)據(jù)同步更新。
總線:維護(hù)解決cpu高速緩存副本數(shù)據(jù)之間一致性問題。
如果狀態(tài)是M的情況下,則使用嗅探機(jī)制通知其他的CPU工作內(nèi)存副本狀態(tài)為I無效狀態(tài),則 刷新主內(nèi)存數(shù)據(jù)到本地中,從而多核cpu數(shù)據(jù)的一致性。
為什么Volatile不能保證原子性
public class VolatileAtomThread extends Thread {
private static volatile int count;
public static void create() {
count++;
}
public static void main(String[] args) {
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Thread tempThread = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
create();
}
});
threads.add(tempThread);
tempThread.start();
}
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(count);
}
}
Volatile為了能夠保證數(shù)據(jù)的可見性,但是不能夠保證原子性,及時(shí)的將工作內(nèi)存的數(shù)據(jù)刷新主內(nèi)存中,導(dǎo)致其他的工作內(nèi)存的數(shù)據(jù)變?yōu)闊o效狀態(tài),其他工作內(nèi)存做的count++操作等于就是無效丟失了,這是為什么我們加上Volatile count結(jié)果在小于10000以內(nèi)。
JMM中的重排序及內(nèi)存屏障
public class ReorderThread {
private static int a = 0, b = 0;
private static int x = 0, y = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
// a=1 x=b (x=0,y=1, y=0,x=1 x=0 y=0 x=1 ,y=1)
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 & y == 0) {0
break;
}
}
}
}
什么是重排序
Java內(nèi)存模型允許編譯器和處理器對(duì)指令代碼實(shí)現(xiàn)重排序提高運(yùn)行的效率,只會(huì)對(duì)不存在的數(shù)據(jù)依賴的指令實(shí)現(xiàn)重排序,在單線程的情況下重排序保證最終執(zhí)行的結(jié)果與程序順序執(zhí)行結(jié)果一致性。
重排序產(chǎn)生的原因
當(dāng)我們的CPU寫入緩存的時(shí)候發(fā)現(xiàn)緩存區(qū)正在被其他cpu站有的情況下,為了能夠提高CPU處理的性能可能將后面的讀緩存命令優(yōu)先執(zhí)行。
注意:不是隨便重排序,需要遵循as-ifserial語義。
as-ifserial:不管怎么重排序(編譯器和處理器為了提高并行的效率)
單線程程序執(zhí)行結(jié)果不會(huì)發(fā)生改變的。
也就是我們編譯器與處理器不會(huì)對(duì)存在數(shù)據(jù)依賴的關(guān)系操作做重排序。
CPU指令重排序優(yōu)化的過程存在問題
as-ifserial 單線程程序執(zhí)行結(jié)果不會(huì)發(fā)生改變的,但是在多核多線程的情況下
指令邏輯無法分辨因果關(guān)系,可能會(huì)存在一個(gè)亂序中心問題,導(dǎo)致程序執(zhí)行結(jié)果錯(cuò)誤。
public class ReorderThread {
private static int a = 0, b = 0;
private static int x = 0, y = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
// a=1 x=b (x=0,y=1, y=0,x=1 x=0 y=0 x=1 ,y=1)
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 & y == 0) {
break;
}
}
}
}
內(nèi)存屏障解決重排序
處理器提供了兩個(gè)內(nèi)存屏蔽指令,解決以上存在的問題
1.寫內(nèi)存屏障:在指令后插入Stroe Barrier ,能夠讓寫入緩存中的最新數(shù)據(jù)更新寫入
主內(nèi)存中,讓其他線程可見。
這種強(qiáng)制寫入主內(nèi)存,這種現(xiàn)實(shí)調(diào)用,Cpu就不會(huì)因?yàn)樾阅艿目紤]對(duì)指令重排序。
2.讀內(nèi)存屏障:在指令前插入load Barrier ,可以讓告訴緩存中的數(shù)據(jù)失效,強(qiáng)制
從新主內(nèi)存加載數(shù)據(jù)
強(qiáng)制讀取主內(nèi)存,讓cpu緩存與主內(nèi)存保持一致,避免緩存導(dǎo)致的一致性問題。
public class ReorderThread {
private static int a = 0, b = 0;
private static int x = 0, y = 0;
public static void main(String[] args) throws InterruptedException {
int i = 0;
while (true) {
i++;
a = 0;
b = 0;
x = 0;
y = 0;
// a=1 x=b (x=0,y=1, y=0,x=1 x=0 y=0 x=1 ,y=1)
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次(" + x + "," + y + ")");
if (x == 0 & y == 0) {
break;
}
}
}
}
手動(dòng)插入內(nèi)存屏障
public class UnSafeUtils {
public static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
return null;
}
}
}
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
//插入寫內(nèi)存屏障
try {
// 手動(dòng)插入一個(gè)內(nèi)存屏障
UnSafeUtils.getUnsafe().storeFence();
} catch (Exception e) {
}
x = b;
}
});
雙重檢驗(yàn)鎖為什么需要加上volatile
public class Singleton03 {
private static volatile Singleton03 singleton03;
public static Singleton03 getInstance() {
// 第一次檢查
if (singleton03 == null) {
//第二次檢查
synchronized (Singleton03.class) {
if (singleton03 == null) {
singleton03 = new Singleton03();
}
}
}
return singleton03;
}
public static void main(String[] args) {
Singleton03 instance1 = Singleton03.getInstance();
Singleton03 instance2 = Singleton03.getInstance();
System.out.println(instance1==instance2);
}
}
注意:因?yàn)槲覀冊(cè)趎ew操作 singleton03 = new Singleton03(),存在重排序的問題。
可以采用 javap -c 查看字節(jié)碼
- 分配對(duì)象的內(nèi)存空間
memory=allocate();
調(diào)用構(gòu)造函數(shù)初始化
將對(duì)象復(fù)制給變量
第二步和第三步流程存在重排序也有可能先執(zhí)行我們的,將對(duì)象復(fù)制給變量,在執(zhí)行
調(diào)用構(gòu)造函數(shù)初始化,導(dǎo)致另外一個(gè)線程獲取到該對(duì)象不為空,但是該改造函數(shù)沒有初始化,
所以就報(bào)錯(cuò)了 。就是另外一個(gè)線程拿到的是一個(gè)不完整的對(duì)象。
volatile存在的偽共享的問題
Cpu會(huì)以緩存行的形式讀取主內(nèi)存中數(shù)據(jù),緩存行的大小為2的冪次數(shù)字節(jié),
一般的情況下是為64個(gè)字節(jié)。
如果該變量共享到同一個(gè)緩存行,就會(huì)影響到整理性能。
例如:線程1修改了long類型變量A,long類型定義變量占用8個(gè)字節(jié),在由于
緩存一致性協(xié)議,線程2的變量A副本會(huì)失效,線程2在讀取主內(nèi)存中的數(shù)據(jù)的時(shí)候,
以緩存行的形式讀取,無意間將主內(nèi)存中的共享變量B也讀取到內(nèi)存中,而化主內(nèi)存
中的變量B沒有發(fā)生變化。

public class FalseShareTest implements Runnable {
// 定義4和線程
public static int NUM_THREADS = 4;
// 遞增+1
public final static long ITERATIONS = 500L * 1000L * 1000L;
private final int arrayIndex;
// 定義一個(gè) VolatileLong數(shù)組
private static VolatileLong[] longs;
// 計(jì)算時(shí)間
public static long SUM_TIME = 0l;
public FalseShareTest(final int arrayIndex) {
this.arrayIndex = arrayIndex;
}
public static void main(final String[] args) throws Exception {
for (int j = 0; j < 10; j++) {
System.out.println(j);
if (args.length == 1) {
NUM_THREADS = Integer.parseInt(args[0]);
}
longs = new VolatileLong[NUM_THREADS];
for (int i = 0; i < longs.length; i++) {
longs[i] = new VolatileLong();
}
final long start = System.nanoTime();
runTest();
final long end = System.nanoTime();
SUM_TIME += end - start;
}
System.out.println("平均耗時(shí):" + SUM_TIME / 10);
}
private static void runTest() throws InterruptedException {
Thread[] threads = new Thread[NUM_THREADS];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new FalseShareTest(i));
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
}
public void run() {
long i = ITERATIONS + 1;
while (0 != --i) {
longs[arrayIndex].value = i;
}
}
// @sun.misc.Contended
public final static class VolatileLong extends AbstractPaddingObject {
public volatile long value = 0L;
// public long p1, p2, p3, p4, p5, p6;
}
}
使用緩存行填充方案避免為共享
Jdk1.6中實(shí)現(xiàn)方案
public final static class VolatileLong{
public volatile long value = 0L;
public long p1, p2, p3, p4, p5, p6;
}
定義p1-6 加上value 一共占用56個(gè)字節(jié) ,在加上VolatileLong類中頭占用8個(gè)字節(jié)一共就是占用64個(gè)字節(jié)。
注意:在Jdk1.7開始對(duì)該代碼做優(yōu)化了,會(huì)導(dǎo)致p1-p6無效,所以必須要寫一個(gè)類單獨(dú)繼承。
Jdk1.7中實(shí)現(xiàn)方案
public final static class VolatileLong extends AbstractPaddingObject {
public volatile long value = 0L;
public long p1, p2, p3, p4, p5, p6;
}
public class AbstractPaddingObject {
public long p1, p2, p3, p4, p5, p6;
}
@sun.misc.Contended
可以直接在類上加上該注解@sun.misc.Contended,啟動(dòng)的時(shí)候需要加上該參數(shù)-XX:-RestrictContended
- ConcurrentHashMap中的CounterCell

synchronized 與volatile存在的區(qū)別
1.Volatile保證線程可見性,當(dāng)工作內(nèi)存中副本數(shù)據(jù)無效之后,主動(dòng)讀取主內(nèi)存中數(shù)據(jù)
2.Volatile可以禁止重排序的問題,底層內(nèi)存屏障。
3.Volatile不會(huì)導(dǎo)致線程阻塞,不能夠保證線程安全問題,synchronized 會(huì)導(dǎo)致線程阻塞
能夠保證線程安全問題。