1.概念
- volatile是java虛擬機提供的輕量級的同步機制,有三大特性:可見性,不保證原子性,禁止指令重排,
1.可見性
在了解可見性之前,你首先要知道JMM(java記憶體模型),
JMM規定了所有的變數都存盤在主記憶體(Main Memory)中,每個執行緒還有自己的作業記憶體(Working Memory),執行緒的作業記憶體中保存了該執行緒使用到的變數的主記憶體的副本拷貝,執行緒對變數的所有操作(讀取、賦值等)都必須在作業記憶體中進行,而不能直接讀寫主記憶體中的變數,不同的執行緒之間也無法直接訪問對方作業記憶體中的變數,執行緒之間值的傳遞都需要通過主記憶體來完成,

可見性是指:在并發編程中,當A執行緒修改了作業記憶體中的值并寫回主記憶體時,及時通知其他執行緒,重新去主記憶體去取最新的值,
package com.sk.Multithreading;
class Data {
int temp = 0;
void add() {
this.temp = 1;
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {//修改temp的執行緒
System.out.println(Thread.currentThread().getName()+"\t 準備修改");
data.add();
System.out.println(Thread.currentThread().getName()+"\t 修改完成");
System.out.println(data.temp);
},"玉無雙").start();
//第二個執行緒是main執行緒
while (data.temp==0){
//一直回圈,直到temp的值變為1
}
System.out.println(Thread.currentThread().getName()+"\t 程式結束");
}
}

????看運行結果,我們明顯看出temp的值被修改,但是程式并沒有結束,main執行緒中的值還是0,程式還在回圈里,并沒有結束,
????當我們加上volatile關鍵字,接下來就是見證奇跡的時刻!
package com.sk.Multithreading;
class Data {
volatile int temp = 0;
void add() {
this.temp = 1;
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {//修改temp的執行緒
System.out.println(Thread.currentThread().getName()+"\t 準備修改");
data.add();
System.out.println(Thread.currentThread().getName()+"\t 修改完成");
System.out.println(data.temp);
},"玉無雙").start();
//第二個執行緒是main執行緒
while (data.temp==0){
//一直回圈,直到temp的值變為1
}
System.out.println(Thread.currentThread().getName()+"\t 程式結束");
}
}


這里明顯看出,無雙執行緒修改temp值完成后,main執行緒能拿到修改后temp的值,這就是可見性!
2.不保證原子性
2.1 原子性:即一個操作或者多個操作,要么全部執行并且執行的程序不會被任何因素打斷,要么就都不執行,
2.2先看例子
package com.sk.Multithreading;
class Data {
volatile int num = 0;
void add() {
num++;
}
}
public class Multithreading {
public static void main(String[] args) {
Data data = new Data();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
data.add();
}
}, "thread" + i).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(data.num);
}
}
計算結果(可以將回圈次數增加效果更明顯)

2.3原因
在num++,這個自增操作,可以分成三步,獲取num的值,加1,再賦值給num,在多執行緒情況下,a執行緒先取到num值為0,做自增操作,num值變為1,正要寫回主記憶體時被掛起;b執行緒也取到num值為0(此時a執行緒還未將值更新回主物理記憶體),b執行緒也做自增操作,b作業記憶體中的num值變為1,b執行緒將值更新回主記憶體,突然啪的一下啊很快,a執行緒被喚醒,a執行緒沒來得及接收到主記憶體的值已經更新,就將自己作業記憶體的值寫回主記憶體,導致了寫覆寫,
2.4解決原子性問題
1.加synchronized(不建議)
package com.sk.Multithreading;
class Data {
volatile int num = 0;
void add() {
num++;
}
}
public class Multithreading {
public static void main(String[] args) {
Data data = new Data();
long start = System.currentTimeMillis();
for (int i = 0; i < 2000; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
synchronized (data) {
data.add();
}
}
}, "thread" + i).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
System.out.println(data.num);
}
}
2.原子類(AtomicInteger),效率高于synchronized
package com.sk.Multithreading;
import java.util.concurrent.atomic.AtomicInteger;
class Data {
volatile AtomicInteger num = new AtomicInteger();
void add() {
num.getAndIncrement();
}
}
public class Multithreading {
public static void main(String[] args) {
Data data = new Data();
long start = System.currentTimeMillis();
for (int i = 0; i < 2000; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
data.add();
}
}, "thread" + i).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
System.out.println(data.num);
}
}
3.有序性(禁止指令重排)
3.1什么是指令重排?
????指令重排是指編譯器和處理器在不影響代碼單執行緒執行結果的前提下,對源代碼的指令進行重新排序執行,這種重排序執行是一種優化手段,目的是為了處理器內部的運算單元能盡量被充分利用,提升程式的整體運行效率,
??例子
package com.sk.Multithreading;
public class test {
int x = 0;//1
int y = 1;//2
int s = x + y;//3
}
多執行緒的情況下執行的順序可能是2,1,3
3.2怎么保證有序性
????volatile的底層是使用記憶體屏障來保證有序性的,寫volatile變數時,可以確保volatile寫之前的操作不會被編譯器重排序到volatile寫之后,讀volatile變數時,可以確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前,
int x = 0;//1
int y = 1;//2
volatile int s = 3;//3
int k = 4;//4
4不會在3前面,1,2也不會在3后面執行(1和2順序不保證)
????volatile變數進行寫操作時,寫操作后加入store屏障指令,將共享變數寫回主記憶體;讀操作時,會在讀操作之前加一個load屏障指令,從主記憶體中讀取共享變數的值,
????“觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的匯編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令”
????lock前綴指令實際上相當于一個記憶體屏障(也稱記憶體柵欄),記憶體屏障會提供3個功能:
1)它確保指令重排序時不會把其后面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的后面;即在執行到記憶體屏障這句指令時,在它前面的操作已經全部完成;
2)它會強制將對快取的修改操作立即寫入主存;
3)如果是寫操作,它會導致其他CPU中對應的快取行無效,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/259056.html
標籤:其他
上一篇:給你的SpringBoot專案定制一個牛年專屬banner吧
下一篇:洛谷P1426 小魚會有危險嗎
