JUC學習之Volatile和原子性問題

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/qq_34560242/article/details/81120511

Volatile 關鍵字

當多個線程操作共享數據時,可保存線程內存之間數據可見,還可防止指令重排序。相對於synchronized 是一種更爲輕量級的同步策略。

public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while(true){
            if(td.isFlag()){
                System.out.println("----hello world----");
                break;
            }
        }
    }
}
class ThreadDemo implements Runnable {
    // 問題變量
    private volatile boolean flag = false;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }
    public boolean isFlag() {
        return flag;
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

當flag不用Volatile關鍵字修飾輸出:
    flag=true

當flag使用Volatile關鍵字修飾輸出:
    ----hello world----
    flag=true

上面代碼可以看出,如果有多條線程同時操作同享數據,如果不使用Volatile關鍵字修飾,就可能出現內存可見性問題。

注意:

  • 1、volatile 不具備“互斥性”,不像synchronized關鍵字:多線程情況下當其中一條線程佔據CPU資源之後,其它線程需要阻塞。
  • 2、volatile 不保證變量的“原子性”。

原子性問題

原子性問題,例如:i++

int i = 10;
i = i++;
System.out.println(i);

輸出結果:10

上述代碼實際的底層實現:
int temp = i;
i = i + 1;
i = temp

Volatile 關鍵字不保證“原子性”測試

public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();   
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }   
}
class AtomicDemo implements Runnable{
    // 這裏變量使用Volatile關鍵字修飾
    private volatile int serialNumber = 0;
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(getSerialNumber());
    }
    public int getSerialNumber(){
        return serialNumber++;
    }
}
輸出結果:(兩個相同的結果)
0
8
2
7
6
5
3
1
1
4

由上述結果
可以看到,即使變量使用Volatile關鍵字修飾之後,多線程環境下仍然有出現問題的可能性。

如何解決原子性問題

原子變量:JDK1.5以後 java.util.concurrent.atomic 包下提供有原子變量。

  • 1、使用Volatile關鍵字保證變量內存可見性
  • 2、使用CAS(Compare-And-Swap)算法保證變量原子性。CAS算法是硬件對於併發操作共享數據的支持。
    CAS 包含三個操作數:

    • 內存值 V
    • 預估值 A
    • 更新值 B

    當且僅當 V == A 時,V = B。否則,則不做任何操作。

public class Test {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}
class AtomicDemo implements Runnable{
    // 使用原子變量包裝類操作
    private AtomicInteger serialNumber = new AtomicInteger();
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(getSerialNumber());
    }
    public int getSerialNumber(){
        // 自增
        return serialNumber.getAndIncrement();
    }
}
輸出結果:(無論多少次操作結果都不會出現有相同值的情況)
6
9
0
2
3
5
4
7
1
8

模擬CAS算法:

public class TestCompareAndSwap {
    public static void main(String[] args) {
        final CompareAndSwap cas = new CompareAndSwap();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 設置之前先獲取一下內存值
                    int expectedValue = cas.get();
                    boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
                    System.out.println(b);
                }
            }).start();
        }   
    }
}

class CompareAndSwap{
    // 內存值
    private int value;
    /**
     * 獲取內存值
     * @return
     */
    public synchronized int get(){
        return value;
    }
    /**
     * 比較
     * @param expectedValue 預估值
     * @param newValue 新值
     * @return
     */
    public synchronized int compareAndSwap(int expectedValue, int newValue){
        int oldValue = value;
        // 內存值和預估值比較
        if(oldValue == expectedValue){
            // 如果內存值和預估值相同,就把新值賦值給內存值
            this.value = newValue;
        }
        // 返回舊內存值
        return oldValue;
    }
    /**
     * 設置新值
     * @param expectedValue 預估值
     * @param newValue 新值
     * @return 設置結果
     */
    public synchronized boolean compareAndSet(int expectedValue, int newValue){
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

輸出結果:(有成功有失敗)
true
false
true
false
false
true
false
false
true
false
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章