java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)
一、什麼是原子類
- 不可分割性
- 一個操作是不可中斷的,在多線程下也可以保證
- 相比於鎖,原子變量可以把競爭資源縮小到變量級別,粒度更細。
- 通常情況下比鎖的效率更高,但是在高度競爭的情況下性能更低。
以AtomicInteger爲例,看看它的方法
int get();//獲取到當前值
int getAndSet(int newValue) {//獲取到當前值並設置新的值
return U.getAndSetInt(this, VALUE, newValue);
}
int getAndIncrement() {//獲取到當前值並自增
return U.getAndAddInt(this, VALUE, 1);
}
int getAndDecrement() {//獲取到當前值並自減
return U.getAndAddInt(this, VALUE, -1);
}
int getAndAdd(int delta) {//獲取到當前值並增加delta
return U.getAndAddInt(this, VALUE, delta);
}
//最重要的CAS操作:如果當前的值是expectedValue,則設置當前值爲newValue
boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
爲什麼是線程安全的:
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
//getAndIncrement方法就是調用了Unsafe類的getAndAddInt方法,並且通過do while循環自旋CAS。從而保證原子性。
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
常用的原子類和原子數組的實例代碼Demo在我的GitHub上:源碼
二、原子引用類AtomicReference
AtomicReference的作用:和AtomicInteger效果一樣,AtomicInteger可以讓一個整數原子性,AtomicReference可以讓一個對象保證原子性。
看看源碼,也是CAS,熟悉的味道:
利用AtomicReference實現自旋鎖:
public class SpinLock {
private AtomicReference<Thread> sign = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
// 當前沒有線程獲取到鎖的情況下才會跳出while循環,自旋操作
while (!sign.compareAndSet(null, current)) {
System.out.println("自旋獲取失敗,再次嘗試");
}
}
public void unlock() {
Thread current = Thread.currentThread();
// 釋放當前線程的鎖
sign.compareAndSet(current, null);
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始嘗試獲取自旋鎖");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + "獲取到了自旋鎖");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + "釋放了自旋鎖");
}
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
三、AtomicIntegerFieldUpdater把普通變量升級爲原子變量
public class AtomicIntegerFieldUpdaterDemo implements Runnable{
static Candidate tom;
static Candidate peter;
public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater
.newUpdater(Candidate.class, "score");
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
peter.score++;
scoreUpdater.getAndIncrement(tom);
}
}
public static class Candidate {
volatile int score;
}
public static void main(String[] args) throws InterruptedException {
tom=new Candidate();
peter=new Candidate();
AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("普通變量:"+peter.score);
System.out.println("升級後的結果"+ tom.score);
}
}
四、LongAdder累加器
- 高併發下LongAdder比AtomicLong效率高,不過本質是空間換時間
- 競爭激烈的時候,LongAdder把不同線程對應到不同cell上進行修改,降低了衝突概率,利用了多段鎖的概念。提高了併發。
AtomicLong爲什麼更慢:
因爲它每一次操作都需要flush和refresh。每一次修改了值都需要把當前值從線程緩存刷到主內存,再同步到每一個線程的內存。在core1中修改了值,需要把值從local cache flush到共享內存(主內存),再從主內存refresh到每個線程的local cache。
LongAdder的改進:
競爭激烈的情況下,每個線程在自己的內存中計算,互相不干擾,也不需要同步每一次修改過後的值。只需要在計算結束後把每個線程的結果加起來。LongAdder採用分段累加,內部有一個base變量,和一個Cell[]數組。
- 競爭不激烈的情況直接累加到base變量
- 競爭激烈的情況,各個線程分散累加到自己的cell[i]槽中
其本質就是空間換時間,
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
// 先嚐試casBase
if ((cs = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[getProbe() & m]) == null ||//當前線程分配的cell[]槽
!(uncontended = c.cas(v = c.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
public long sum() {
Cell[] cs = cells;
long sum = base;
if (cs != null) {
for (Cell c : cs)
if (c != null)
sum += c.value;
}
return sum;
}
五、Accumulator累加器
適用於大量計算、並行計算的場景。
public class LongAccumulatorDemo {
public static void main(String[] args) {
LongAccumulator accumulator = new LongAccumulator((x, y) -> 2 + x * y, 1);
//LongAccumulator accumulator = new LongAccumulator((x, y) -> Math.max(x, y), 1);
ExecutorService executor = Executors.newFixedThreadPool(8);
IntStream.range(1, 10).forEach(i -> executor.submit(() -> accumulator.accumulate(i)));
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println(accumulator.getThenReset());
}
}
- 我的公衆號:Coding摳腚
- 偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。