瞭解jdk源碼-atomic和locks包

1.原子操作類

包路徑:java.util.concurrent.atomic

基礎類其中包括:

布爾類型-AtomicBoolean,整形類型-AtomicInteger,浮點類型-AtomicLong,引用類型-AtomicReference。

其主要理念:由volatile修飾value保證可見性和有序性,使用unsafe進行CAS操作實現原子性操作。

其中主要方法 getAndSet() ,原子性的設置一個新值並返回舊值。在併發情況下,不需要synchronized就可以保證操作的原子性,開銷相比於synchronized小很多,適合非阻塞算法實現併發控制。

升級版:

LongAdder和DoubleAdder類分別用戶對Long和Double類型的累加器(只能操作對數值的加減),類中有base變量和數組cell,當併發競爭不嚴重時,直接對base通過CAS進行原子操作,當併發競爭嚴重時,將使用CAS操作cell數組,通過sum方法獲取base+cell[]中所有值的和。相當於將一個值,拆分爲多個值,通過增加線程競爭的目標來降低CAS操作的消耗。在高併發下,有更大的吞吐量,但會消耗更大的空間,也是一種空間換時間的做法。

LongAccumulator和DoubleAccumulator是LongAdder和DoubleAdder的升級版,可以自己定製操作,構造器需要傳入一個函數式接口和初始值,當調用accumulate方法時,如果競爭不激烈,會直接通過函數式接口操作傳入long或者double的值並賦給base,如果競爭激烈則直接將值cell數組中。通過get方法獲得時會通過函數式接口計算base和cell數組中的所有值。

使用示例:

package com.ma.vue.boot.atomic;

import org.junit.Test;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

public class AtomicTest {
    private static AtomicInteger atomicInteger= new AtomicInteger();//對於int的原子操作類

    private static LongAdder longAdder = new LongAdder();//從0開始的對於long的原子累加器

    private static LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0);//原始值爲0 相加

    private static LongAccumulator longAccumulator4multiplication = new LongAccumulator((x,y) -> x*y,1);//原始值爲0 相乘

    @Test
    public void testAtomic(){
        ExecutorService service  = Executors.newCachedThreadPool();
        for(int i = 0 ;i < 20 ; i++){
            service.submit(() -> {
                int randomInt = new Random().nextInt(100);
                int andSet = atomicInteger.getAndSet(randomInt);
                System.out.println("atomicInteger.getAndSet old value "+andSet+" new value " +randomInt);
                //結果爲10個20相加爲200
                longAdder.add(10);
                System.out.println("longAdder value : "+longAdder.sum());
                //結果爲 20個2相加 爲40
                longAccumulator.accumulate(2);
                System.out.println("longAccumulator accumulate value : " + longAccumulator.get());
                //結果爲 2的10次方1048576
                longAccumulator4multiplication.accumulate(2);
                System.out.println("longAccumulator4multiplication accumulate value : " + longAccumulator4multiplication.get());
            });
        }
        service.shutdown();
    }
}

2.鎖

包路徑:java.util.concurrent.locks

常用實現類包括ReentrantLock(可重入鎖),ReentrantReadWriteLock(可重入讀寫鎖),StampedLock(版本鎖不可重入)。

這三個類都是基於CAS和QAS的實現,QAS是jdk提供的一個併發同步控制抽象類,即java.util.concurrent.locks.AbstractQueuedSynchronizer,提供了大量方法幫助我們實現自定義鎖。

根據JDK中例子實現的自定義不可重入互斥鎖:

package com.ma.vue.boot.lock;





import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 一個簡單的QAS的實現,不可重入互斥鎖
 */
public class AQSTest implements Lock, Serializable {

    /**
     * 藉助AQS框架實現
     */
    private static class Sync extends AbstractQueuedSynchronizer {
        /**
         * 返回是被處於被鎖狀態
         *
         * @return
         */
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        /**
         * 如果未加鎖則嘗試加鎖
         *
         * @param acquires
         * @return
         */
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /**
         * 嘗試釋放鎖
         * @param releases
         * @return
         */
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        Condition newCondition() { return new ConditionObject(); }

        /**
         * 爲反序列化提供支持
         * @param s
         * @throws IOException
         * @throws ClassNotFoundException
         */
        private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0);//重置鎖狀態
        }
    }

    private final AQSTest.Sync sync = new Sync();

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return new Sync().newCondition();
    }

    public boolean isLocked()         { return sync.isHeldExclusively(); }

}

由於JDK對於synchronized的不斷優化和升級,性能:synchronized=StampedLock>ReentrantReadWriteLock>ReentrantLock

示例:

ReentrantLock的簡單使用:

package com.ma.vue.boot.lock;

import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {
    //可重入鎖,必須手動釋放鎖
    private final ReentrantLock reentrantLock = new ReentrantLock();

    private final ExecutorService service  = Executors.newCachedThreadPool();

    private static int sum = 0;

    //線程計數器
    private static CountDownLatch latch = new CountDownLatch(10);
    @Test
    public void reentrantLockTest(){
        for(int i=0;i<10;i++){
            service.submit(() -> {
                for(int j=0;j<10;j++){
                    reentrantLock.lock();
                    try{
                        sum ++;
                        reentrantLock.lock();
                        try{
                            sum ++;
                        }finally {
                            reentrantLock.unlock();
                        }
                    }finally {
                        reentrantLock.unlock();
                    }
                }
                //每執行完一個線程就將latch減一
                latch.countDown();
            });
        }
        try {
            latch.await();//會等待至計數器爲0被喚醒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(sum);
    }
}

ReentrantLock是可重入互斥鎖的實現,可選擇公平鎖或非公平鎖,非公平鎖吞吐量較高,在較老版本JDK,效率比synchronized關鍵字高,消耗也相對較少。

ReentrantReadWriteLock的簡單使用:

package com.ma.vue.boot.lock;

import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 用來實現併發的集合
 * 當集合預期很大,讀線程比寫線程多 推薦使用
 */
public class ReadWriteLockDictionaryTest {
    private final Map<String, Object> map = new TreeMap<String, Object>();
    //讀寫鎖
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Object getObject(String key) {
        Object o = null;
        r.lock();
        try {
            o = map.get(key);
        } finally {
            r.unlock();
        }
        return o;
    }

    public Object[] getAllKeys() {
        r.lock();
        try {
            return map.keySet().toArray();
        } finally {
            r.unlock();
        }
    }

    public Object put(String key, Object o) {
        w.lock();
        try {
            return map.put(key, o);
        } finally {
            w.unlock();
        }
    }

    public void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }
}
ReentrantReadWriteLock是一箇中可重入讀寫鎖,讀讀共享,讀讀共享,寫寫互斥,讀寫互斥。

StampedLock的使用示例:

package com.ma.vue.boot.lock;

import java.util.concurrent.locks.StampedLock;

public class StampedLockTest {
    private  long sum;
    //讀寫鎖,讀鎖可由樂觀鎖升級爲悲觀鎖,進一步提升性能,但不可重入
    private final StampedLock sl = new StampedLock();

    public void move(long sum){
        long stamp = sl.writeLock();//返回一個版本號
        try {
            this.sum = sum;
        }finally {
            sl.unlockWrite(stamp);
        }
    }

    /**
     * 默認獲取樂觀鎖增加效率,當版本變化時獲取悲觀鎖保證數據的正確
     * @return
     */
    public long read(){
        //獲取一個樂觀讀鎖的版本號
        long stamp = sl.tryOptimisticRead();
        //獲取數據
        long sum = this.sum;
        //對比版本是否變化
        if(!sl.validate(stamp)){
            //版本變化時則獲取悲觀讀鎖
            stamp = sl.readLock();
            try{
                //獲取數據
                sum = this.sum;
            }finally {
                sl.unlockRead(stamp);
            }
        }
        return sum;
    }

    /**
     * 默認使用悲觀讀鎖,當無法升級爲寫鎖時再顯示獲取寫鎖
     * @param newSum
     */
    public void updateIfAtOrigin(long newSum){
        //獲取讀鎖
        long stamp = sl.readLock();
        try {
            while (sum == 0) {
                //嘗試根據版本號將讀鎖升級爲寫鎖,成功返回版本號,失敗返回0
                long ws = sl.tryConvertToWriteLock(stamp);
                if(ws != 0L){
                    //成功則寫入值
                    stamp = ws;
                    this.sum = newSum;
                    break;
                }else{
                    //失敗則釋放讀鎖,獲取寫鎖
                    sl.unlockRead(stamp);
                    stamp = sl.readLock();
                }
            }
        }finally {
            sl.unlock(stamp);
        }
    }
}

StampedLock是一種可重入讀寫鎖,並將讀鎖可由樂觀鎖升級爲悲觀鎖,進一步提升了吞吐量,並且提供了一種悲觀讀鎖升級爲寫鎖的操作方式。

如果想讓線程進行awite和notify操作,可以使用Condition對象進行操作,示例:

package com.ma.vue.boot.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 通過condition 實現一個有界緩衝區
 */
public class ConditionTest {
    private final ReentrantLock lock = new ReentrantLock();

    private final Condition notEmpty = lock.newCondition();

    private final Condition notFull = lock.newCondition();

    final Object [] item = new Object[100];
    //放置元素索引,消耗元素索引,計數器
    int putInx =0 ,takeInx = 0 ,count= 0;

    /**
     * 新增元素,如果緩衝區滿了,則進入等待狀態
     * @param object
     * @throws InterruptedException
     */
    public void put(Object object) throws InterruptedException {
        lock.lock();
        try{
            while (count == item.length)
                notFull.await();
            item[putInx] = object;
            if(++putInx == item.length)
                putInx = 0;
            ++count;
            notFull.signal();
        }finally {
            lock.unlock();
        }
    }

    /**
     * 消耗元素,如果緩衝區空了,則進入等待狀態
     * @return
     * @throws InterruptedException
     */
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();
            Object x = item[takeInx];
            if(++takeInx == item.length)
                takeInx = 0;
            --count;
            notFull.signal();
            return x;
        }finally {
            lock.unlock();
        }
    }
}

 

以上大部分示例都爲JDK源碼中的示例,僅做學習記錄之用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章