1.2.2 線程安全之原子操作CAS

競(jìng)態(tài)條件與臨界區(qū)

public class Demo{
  public int i = 0;
  public void incr(){
    I++;
  }
}

多個(gè)線程訪問(wèn)了相同的資源,向這些資源做了寫操作時(shí),對(duì)執(zhí)行順序有要求。

臨界區(qū): incr 方法內(nèi)部就是臨界區(qū)域,關(guān)鍵部分代碼的多線程并發(fā)執(zhí)行,會(huì)對(duì)執(zhí)行結(jié)果產(chǎn)生影響。

競(jìng)態(tài)條件: 可能發(fā)生在臨界區(qū)域內(nèi)的特殊條件。多線程執(zhí)行incr 方法中的 i++ 關(guān)鍵代碼時(shí),產(chǎn)生了競(jìng)態(tài)條件。

共享資源

  • 如果一段代碼是線程安全的,則它不包含競(jìng)態(tài)條件。只有當(dāng)多個(gè)線程更新共享資源時(shí),才會(huì)發(fā)生競(jìng)態(tài)條件。
  • 棧封閉時(shí),不會(huì)在線程之間共享的變量,都是線程安全的。
  • 局部對(duì)象引用本身不共享,但是引用的對(duì)象存儲(chǔ)在共享堆中。如果方法內(nèi)創(chuàng)建的對(duì)象,只是在方法中傳遞,并且不對(duì)其他線程可用,那么也是線程安全的。
public void someMethod(){
  LocalObject localObject = new LocalObject();
  
  localObject.callMethod();
  method2(localObject);
}

public void method2(LocalObject localObject){
  localObject.setValue("value");
}

判斷規(guī)則:如果創(chuàng)建、使用和處理資源,永遠(yuǎn)不會(huì)逃脫單個(gè)線程的控制,該資源的使用是線程安全的。

不可變對(duì)象

創(chuàng)建不可變的共享對(duì)象在線程間共享時(shí)不會(huì)被修改,從而實(shí)現(xiàn)線程安全。

實(shí)例被創(chuàng)建,value變量就不能再被修改,這就是不可變性。

public class Demo{
  private int value = 0;
  public Demo(int value){
    this.value = value;
  }
  public int getValue(){
        return this.value;
  }
  //不提供對(duì)象內(nèi)屬性的設(shè)置方法
}

原子操作定義

原子操作可以是一個(gè)步驟,也可以是多個(gè)操作步驟,但是其順序不可以被打亂,也不可以被切割而只執(zhí)行其中的一部分(不可中斷性)。

將整個(gè)操作視作一個(gè)整體是原子性的核心特征。

public class Demo{
  public int i = 0;
  public void incr(){
    i++; //進(jìn)行i++的步驟實(shí)際分為三步:1. 加載i 2.計(jì)算+i 3.賦值
  }
  
  public static void main(String[] args) throws InterruptedException {
        Demo ld = new Demo();

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    ld.incr();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.i);
    }
}

結(jié)果輸出后你會(huì)發(fā)現(xiàn)i的值不為20000,就是因?yàn)檫M(jìn)行i++的步驟實(shí)際分為三步,不為原子性操作。

CAS機(jī)制

Compare and swap比較和交換。屬于硬件同步原語(yǔ),處理器提供基本內(nèi)存操作的原子性保證。CAS操作需要輸入兩個(gè)數(shù)值,一個(gè)舊值(期望操作前的值)和一個(gè)新值,在操作期間先比較下舊值有沒(méi)有發(fā)生變化,如果沒(méi)有發(fā)生變化,才交換新值,發(fā)生了變化則不交換。

Java中的sun.misc.Unsafe類,提供了compareAndSwapInt()和compareAndSwapLong()等幾個(gè)方法實(shí)現(xiàn)CAS。

public class LockDemo1 {
    volatile int value = 0;

    static Unsafe unsafe;//直接操控內(nèi)存,修改對(duì)象,數(shù)組內(nèi)存...強(qiáng)大的API
    private static long valueOffset;//偏移量

    static {
        try {
            // 反射技術(shù)獲取unsafe值
            Field field = Unsafe.class.getDeclaredField("theUnsafee");
            field.setAccessible(true);
            unsafe = (Unsafe)field.get(null);
            // 獲取到 value 屬性偏移量(用于定于value屬性在內(nèi)存中的具體地址)
            valueOffset = unsafe.objectFieldOffset(LockDemo1.class
                    .getDeclaredField("value"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void add() {
        // TODO xx00
        // i++;// JAVA 層面三個(gè)步驟
        // CAS + 循環(huán) 重試
        int current;
        do {
            // 操作耗時(shí)的話, 那么 線程就會(huì)占用大量的CPU執(zhí)行時(shí)間
            current = unsafe.getIntVolatile(this, valueOffset);
        } while (!unsafe.compareAndSwapInt(this, valueOffset, current, current + 1));
        // 可能會(huì)失敗
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo1 ld = new LockDemo1();

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    ld.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.value);
    }
}

輸出結(jié)果:20000

當(dāng)然我們只需要理解其中的原理,JDK給我們準(zhǔn)備了現(xiàn)成的API。

保證原子操作的其他方法

  • 利用synchronized
public class LockDemo2 {
    int i = 0;

    public void add() {
        synchronized (this) {
            I++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo2 ld = new LockDemo2();

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    ld.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.i);
    }
}

輸出結(jié)果:20000

  • 利用Lock
public class LockDemo3 {
    volatile int i = 0;

    Lock lock = new ReentrantLock();

    public void add() {
        lock.lock();
        try {
            // TODO  很多業(yè)務(wù)操作
            I++;
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo3 ld = new LockDemo3();

        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    ld.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.i);
    }
}

輸出結(jié)果:20000

J.U.C包內(nèi)的原子操作封裝類
在這里插入圖片描述

// atomic 相關(guān)測(cè)試代碼
public class AtomicTest {
    public static void main(String[] args) throws InterruptedException {
        // 自增
        AtomicInteger atomicInteger = new AtomicInteger(0);
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    atomicInteger.incrementAndGet();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(atomicInteger.get());
    }
}
// LongAccumulator是LongAdder增強(qiáng)版,處理累加之外,可以自行定義其他計(jì)算
public class LongAccumulatorDemo {
    public static void main(String[] args) throws InterruptedException {
        LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                // 返回最大值,這就是自定義的計(jì)算
                return left < right ? left : right;
            }
        }, 0);

        // 1000個(gè)線程
        for (int i = 0; i < 1000; i++) {
            int finalI = I;
            new Thread(() -> {
                accumulator.accumulate(finalI); // 此處實(shí)際就是執(zhí)行上面定義的操作
            }).start();
        }

        Thread.sleep(2000L);
        System.out.println(accumulator.longValue()); // 打印出結(jié)果
    }

}

synchronized、AtomicLong和LongAdder三者的性能比較

// 測(cè)試用例: 同時(shí)運(yùn)行2秒,檢查誰(shuí)的次數(shù)最多
public class LongAdderDemo {
    private long count = 0;

    // 同步代碼塊的方式
    public void testSync() throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                long starttime = System.currentTimeMillis();
                while (System.currentTimeMillis() - starttime < 2000) { // 運(yùn)行兩秒
                    synchronized (this) {
                        ++count;
                    }
                }
                long endtime = System.currentTimeMillis();
                System.out.println("SyncThread spend:" + (endtime - starttime) + "ms" + " v" + count);
            }).start();
        }
    }

    // Atomic方式
    private AtomicLong acount = new AtomicLong(0L);

    public void testAtomic() throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                long starttime = System.currentTimeMillis();
                while (System.currentTimeMillis() - starttime < 2000) { // 運(yùn)行兩秒
                    acount.incrementAndGet(); // acount++;
                }
                long endtime = System.currentTimeMillis();
                System.out.println("AtomicThread spend:" + (endtime - starttime) + "ms" + " v-" + acount.incrementAndGet());
            }).start();
        }
    }

    // LongAdder 方式
    private LongAdder lacount = new LongAdder();
    public void testLongAdder() throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                long starttime = System.currentTimeMillis();
                while (System.currentTimeMillis() - starttime < 2000) { // 運(yùn)行兩秒
                    lacount.increment();
                }
                long endtime = System.currentTimeMillis();
                System.out.println("LongAdderThread spend:" + (endtime - starttime) + "ms" + " v-" + lacount.sum());
            }).start();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LongAdderDemo demo = new LongAdderDemo();
        demo.testSync();
        demo.testAtomic();
        demo.testLongAdder();
    }
}
輸出結(jié)果:
SyncThread spend:2000ms v22957789
AtomicThread spend:2000ms v-36239545
AtomicThread spend:2000ms v-36309497
SyncThread spend:2007ms v22957790
AtomicThread spend:2004ms v-36332774
SyncThread spend:2015ms v22957791
LongAdderThread spend:2000ms v-40279545
LongAdderThread spend:2000ms v-40835325
LongAdderThread spend:2000ms v-40855171

由此可以看出在相同時(shí)間內(nèi)三個(gè)方法同時(shí)累加并且互相爭(zhēng)搶CPU的性能,最終運(yùn)行結(jié)果是LongAdder在兩秒運(yùn)行次數(shù)最多。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容