volatile理解
Java語言是支持多執行緒的,為了解決執行緒并發的問題,在語言內部引入了 同步塊 和volatile 關鍵字機制,volatile具有synchronized關鍵字的“可見性”,volatile變數對于每次使用,執行緒都能得到當前volatile變數的最新值,但是沒有synchronized關鍵字的“并發正確性”,也就是說不保證執行緒執行的有序性,
特性
1、保證記憶體可見性
各個執行緒對主記憶體中共享變數的操作都是各個執行緒各自拷貝到自己的作業記憶體操作后再寫回主記憶體中的,這就可能存在一個執行緒AAA修改了共享變數X的值還未寫回主記憶體中時 ,另外一個執行緒BBB又對記憶體中的一個共享變數X進行操作,但此時A執行緒作業記憶體中的共享比那里X對執行緒B來說并不不可見.這種作業記憶體與主記憶體同步延遲現象就造成了可見性問題,Java提供了volatile來保證可見性,當一個變數被volatile修飾后,表示著執行緒本地記憶體無效,當一個執行緒修改共享變數后他會立即被更新到主記憶體中,其他執行緒讀取共享變數時,會直接從主記憶體中讀取,
2、不保證原子性
原子性在一個操作是不可中斷的,要么全部執行成功要么全部執行失敗,如a++,a+=1就不是原子性操作,volatile不能保證原子性,
3、禁止指令重排序
計算機在執行程式時,為了提高性能,編譯器和處理器常常會做指令重排:
1. 單執行緒環境里面確保程式最終執行結果和代碼順序執行的結果一致.
2. 處理器在進行重新排序是必須要考慮指令之間的資料依賴性
3. 多執行緒環境中執行緒交替執行,由于編譯器優化重排的存在,兩個執行緒使用的變數能否保持一致性是無法確定的,結果無法預測
代碼
保證記憶體可見性
package com.raicho.mianshi.myvolatile;
public class MyVolatileVisibility {
// private int i;
private volatile int i;
public void changeI(int i) {
this.i = i;
}
public static void main(String[] args) {
// System.out.println("沒有加volatile關鍵字");
MyVolatileVisibility myVolatile = new MyVolatileVisibility();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myVolatile.changeI(1);
System.out.println(Thread.currentThread().getName()+"修改了i="+myVolatile.i);
},"執行緒1 ").start();
System.out.println( Thread.currentThread().getName()+"訪問 i = "+ myVolatile.i);
while (myVolatile.i == 0) {
}
}
}

當沒有加volatile關鍵字時,當主執行緒先訪問了i值為0,后執行緒1再進行修改i=1,回到mian執行緒,并不能察覺到i的值修改為1,然而一直在while回圈不能結束,加上volatile關鍵字就能夠檢測到其他執行緒已經將i的值修改為1,結束程式,
不保證原子性
package com.raicho.mianshi.myvolatile;
public class VolatileAtomicity {
volatile int number = 0;
public void addNum(){
number++;
}
public static void main(String[] args) {
VolatileAtomicity va = new VolatileAtomicity();
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
va.addNum();
}
},String.valueOf(i)).start();
}
//等等20條執行緒完成
while (Thread.activeCount() >2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" number = "+va.number);
}
}

通過代碼驗證并最終number并不能達到20000,證明volatile并不保證原子性操作
解決方案
- 在addNum()方法上加鎖synchronized關鍵字,肯定是可以解決的,但是synchronized加鎖太重了,嚴重降低效率
- 使用AtomicInteger類
package com.raicho.mianshi.myvolatile;
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileAtomicity {
volatile int number = 0;
public void addNum(){
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addNumAtomicInteger(){
atomicInteger.getAndIncrement();
}
public static void main(String[] args) {
VolatileAtomicity va = new VolatileAtomicity();
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
//va.addNum();
va.addNumAtomicInteger();
}
},String.valueOf(i)).start();
}
//等等20條執行緒完成
while (Thread.activeCount() >2){
Thread.yield();
}
// System.out.println(Thread.currentThread().getName()+" number = "+va.number);
System.out.println(Thread.currentThread().getName()+" number = "+va.atomicInteger);
}
}

禁止指令重排序
public void mySort(){
int x=11;//陳述句1
int y=12;//陳述句2
x=x+5;//陳述句3
y=x*x;//陳述句4
}
重新排序后可能會變為
1234
2134
1324
問題:
請問陳述句4 可以重排后變成第一條碼?
存在資料的依賴性 沒辦法排到第一個
單例模式中使用雙重檢測機制
public class SingletonDemo {
private static volatile SingletonDemo instance=null;
private SingletonDemo(){
System.out.println(Thread.currentThread().getName()+"\t 構造方法");
}
/**
* 雙重檢測機制
* @return
*/
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 1; i <=10; i++) {
new Thread(() ->{
SingletonDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
在多執行緒下,不加volatile關鍵字也可能出現指令重排的情況,是執行緒不安全的
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/183582.html
標籤:Java
