目錄
一、對volatile的理解
1.JMM
2.volatile
3.你在哪些地方用到過volatile?
二、CAS你知道嗎?
2.1 比較并交換
2.2 CAS底層原理 對UnSafe的理解
2.2.1 atomicInteger.getAndIncrement();
2.2.2 Unsafe
2.2.3 CAS是什么?Unsafe類+CAS思想(自旋)
2.3 CAS缺點
三、原子類AtomicInteger的ABA問題談談?原子更新參考知道嗎?
3.1 ABA問題是怎么產生的?
3.2 原子參考
3.3 時間戳原子參考
一、對volatile的理解
1.JMM
1.1可見性、原子性、VolatileDemo代碼演示可見性+原子性代碼、有序性
1.2 JMM 執行緒安全性獲得保證
作業記憶體與主記憶體同步延遲現象導致的可見性問題:可以使用synchronized或volatile關鍵字解決,它們都可以使一個執行緒修改后的變數立即對其他執行緒可見,
對于指令重排導致的可見性問題和有序性問題:可以利用volatile關鍵字解決,因為volatile的另外一個作用就是禁止重排序優化,

2.volatile
是Java虛擬機提供的輕量級的同步機制(乞丐版的synchronized):保證可見性,不保證原子性,禁止指令重排
代碼演示:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData{//MyData.java ===>MyData.class ===>jvm 位元組碼
volatile int number = 0;
public void addT060(){
this.number=60;
}
//請注意,此時number前面是加了volatile關鍵字修飾的,volatile不保證原子性
public void addPlusPlus(){
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtomic(){
atomicInteger.getAndIncrement();
}
}
/**
* 1 驗證volatile 的可見性
* 1.1 假如int number = 0;number變數之前根本沒有添加volatile關鍵字修飾,沒有可見性
* 1.2 添加了volatile,可以解決可見性問題
*
* 2 驗證volatile不保證原子性
* 2.1 原子性指的是什么意思?
* 不可分割,完整性,也即某個執行緒正在做某個具體業務時,中間不可以被加塞或者被分割,需要整體完整
* 要么同時成功,要么同時失敗
* 2.2 volatile 不保證原子性的案例演示
* 2.3 why? 數值少于20000 出現了丟失寫值的情況(寫覆寫)要寫的時候有可能有的執行緒被掛起了
* 2.4 如何解決原子性?
* * 加sync
* * 使用我們的juc下AtomicInteger
*/
public class VolatileDemo {
public static void main(String[] args) { //main是一切方法的運行入口
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j=1;j<=1000;j++){
myData.addPlusPlus();
myData.addAtomic();
}
}, String.valueOf(i)).start();
}
//需要等待上面20個執行緒全部計算完成后,再用main執行緒取得最終的結果值看是多少?
//暫停一會執行緒
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t int type, finally number value:"+myData.number);//volatile如果保證原子性的話,這個值應該是2W,而執行結果卻不是(某次為19468)
System.out.println(Thread.currentThread().getName()+"\t AtomicInteger type, finally number value:"+myData.atomicInteger);
}
public static void seeOkByVolatile() {
MyData myData = new MyData();
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "\t come in");
//暫停一會執行緒
try {
TimeUnit.SECONDS.sleep(3);//3秒鐘之后執行將number加到60,可是這時候main執行緒還在那等著,不知道!
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addT060();
System.out.println(Thread.currentThread().getName() + "\t updated number value:" + myData.number);
},"AAA").start();
//第2個執行緒就是我們的main執行緒
while (myData.number==0){
//main執行緒一直再這里回圈等待,直到number值不再等于零
}
System.out.println(Thread.currentThread().getName() + "\t mission is over");
}
}
Java 匯編案例解釋:

禁止指令重排小總結:


3.你在哪些地方用到過volatile?
3.1 單例模式DCL代碼
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 我是構造方法SingletonDemo()");
}
//DCL (Double Check Lock 雙端檢鎖機制)
//以下這種寫法,99.99%的正確性,底層有可能出現指令重排
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class){
if (instance==null){
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
//單執行緒(main執行緒的操作動作.................)
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println();
// System.out.println();
// System.out.println();
//并發多執行緒后,情況發生了很大的變化
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}
3.2 單例模式volatile分析

但是指令重排只會保證串行語意的執行的一致性(單執行緒),但并不會關心多執行緒間的語意一致性,
所以當一條執行緒訪問instance不為null時,由于instance實體未必已初始化完成,也就造成了執行緒安全問題,
在instance前面加上volatile,禁止指令重排
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 我是構造方法SingletonDemo()");
}
//DCL (Double Check Lock 雙端檢鎖機制)
//以下這種寫法,99.99%的正確性,底層有可能出現指令重排
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class){
if (instance==null){
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
//單執行緒(main執行緒的操作動作.................)
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
// System.out.println();
// System.out.println();
// System.out.println();
//并發多執行緒后,情況發生了很大的變化
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}
二、CAS你知道嗎?
2.1 比較并交換

如果執行緒的期望值和物理記憶體的真實值一樣,我就修改我的更新值,回傳true 進行修改,
2.2 CAS底層原理 對UnSafe的理解
2.2.1 atomicInteger.getAndIncrement();


2.2.2 Unsafe

CAS是比較并交換,它保證原子性靠的是底層的Unsafe類

2.2.3 CAS是什么?Unsafe類+CAS思想(自旋)
2.2.3.1 unsafe.getAndAddInt
CAS全稱為Compare-And-Swap,它是一條CPU并發原語,
它的功能是判斷記憶體某個位置的值是否為預期值,如果是則更改為新的值,這個程序是原子的,
CAS并發原語體現在JAVA語言中就是sun.misc.Unsafe類中的各個方法,呼叫UnSafe類中的CAS方法,JVM會幫我們實作出CAS匯編指令,這是一種完全依賴于硬體的功能,通過它實作了原子操作,再次強調,由于CAS是一種系統原語,原語屬于作業系統用語范疇,是由若干條指令組成的,用于完成某個功能的一個程序,并且原語的執行必須是連續的,在執行程序中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的資料不一致問題,

//unsafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2);//獲取當前物件var1在var2地址上的值是多少,賦值給var5 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//比較并交換,比較快照值和物理記憶體真實值是否相等 return var5; }


2.2.3.2 底層匯編

2.2.3.3 簡單版小總結
CAS(CompareAndSwap)
比較當前作業記憶體中的值和主記憶體中的值,如果相同則執行規定操作,否則繼續比較直到主記憶體和作業記憶體中的值一致為止,
CAS應用
CAS有3個運算元,記憶體值V,舊的預期值A,要修改的更新值B,
當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什么都不做,
2.3 CAS缺點
1)回圈時間長開銷很大,
我們可以看到getAndAddInt方法執行時,有個do while

如果CAS失敗,會一直進行嘗試,如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷,
2)只能保證一個共享變數的原子操作,
當對一個共享變數執行操作時,我們可以使用回圈CAS的方式來保證原子操作,但是,對多個共享變數操作時,回圈CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性,
3)引出來ABA問題(重點)
三、原子類AtomicInteger的ABA問題談談?原子更新參考知道嗎?
CAS -----> Unsafe ------> CAS底層思想 -------> ABA --------> 原子參考更新 ----------> 如何規避ABA問題
* ABA :貍貓換太子
* 解決ABA問題??? 理解原子參考+新增一種機制,那就是修改版本號(類似時間戳)
CAS不夠???
3.1 ABA問題是怎么產生的?
答:CAS會導致“ABA”問題,CAS演算法實作一個重要前提需要取出記憶體中某時刻的資料并在當下時刻比較并替換,那么在這個時間差類會導致資料的變化,
比如說一個執行緒one從記憶體位置V中取出A,這時候另一個執行緒two也從記憶體中取出A,并且執行緒two進行了一些操作將值變成了B,然后執行緒two又將V位置的資料變成A,這時候執行緒one進行CAS操作發現記憶體中仍然是A,然后執行緒one操作成功,
盡管執行緒one的CAS操作成功,但是并不代表這個程序就是沒有問題的,
3.2 原子參考
import java.util.concurrent.atomic.AtomicReference;
class User{
String userName;
int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("z3",22);
User li4 = new User("li4", 25);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
}
}
3.3 時間戳原子參考

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo { //ABA問題的解決 AtomicStampedReference --->帶時間戳的原子參考
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("===============以下是ABA問題的產生====================");
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start(); //t1干了一次ABA
new Thread(()->{
try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
},"t2").start();
//暫停一會兒執行緒
try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){ e.printStackTrace(); }
System.out.println("===============以下是ABA問題的解決====================");
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本號:" + stamp);
//暫停1秒鐘t3執行緒
try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t第2次版本號:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "\t第3次版本號:" + atomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本號:" + stamp);
//暫停3秒鐘t4執行緒,保證上面的t3執行緒完成了一次ABA操作
try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
boolean res = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否" + res+"\t當前最新實際版本號:"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t當前實際最新值:"+atomicStampedReference.getReference());
},"t4").start();
}
}
執行結果如下:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/249029.html
標籤:其他
