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源碼中的示例,僅做學習記錄之用。