只談?wù)劊蝗采w
CAS
悲觀鎖,假設(shè)執(zhí)行當(dāng)前區(qū)域代碼都會產(chǎn)生沖突,為了解決沖突,線程A
獲取鎖時,其它線程會被阻塞處于停頓狀態(tài),直到鎖釋放(可能會造成死鎖)。CAS
概念屬于一種樂觀鎖的策略(或者稱為無鎖),假設(shè)執(zhí)行當(dāng)前區(qū)域代碼都不會發(fā)生沖突,不會發(fā)生沖突就自然沒有線程阻塞,也不會產(chǎn)生死鎖問題。如果出現(xiàn)了沖突,CAS(compare and swap) 比較和替換
就會不斷的去重試,直到當(dāng)前操作沒有沖突。
CAS 的 ABA 問題
- 線程1從內(nèi)存
M
位置取出A
- 線程2從內(nèi)存
M
位置取出A
- 線程2做了預(yù)期值比較,將
A
替換為B
并放到M
位置 - 線程2從內(nèi)存
M
位置取出B
- 線程2做了預(yù)期值比較,將
B
替換為A
并放到M
位置 - 線程1做了預(yù)期值比較,將
A
替換為C
并放到M
位置
此時線程1影響了線程2的狀態(tài),也就發(fā)生了ABA
的問題。所以為了解決樂觀鎖并發(fā)時造成的ABA
問題,都會使用AtomicStampedReference 類
或者AtomicMarkableReference 類
。
volatile
volatile
從主內(nèi)存中加載到線程工作內(nèi)存中的值是最新的。也就是說它解決的是多線程并發(fā)變量讀時的可見性問題,但無法保證訪問變量的原子性。而且volatile
只能修飾變量。
原子基本類型
-
AtomicBoolean
保證布爾值的原子性 -
AtomicInteger
保證整型的原子性 -
AtomicLong
保證長整型的原子性
原子數(shù)組
-
AtomicIntegerArray
保證整型數(shù)組的原子性 -
AtomicLongArray
保證長整型數(shù)組原子性
原子字段
-
AtomicIntegerFieldUpdater
保證整型的字段更新 -
AtomicLongFieldUpdater
保證長整型的字段更新
使用原子字段類時,所聲明的字段類型必須為
volatile
使用方法如下:
private int sum = 100;
private volatile int sum1 = 100;
// 當(dāng) sum 或 sum1 為100 時只允許有一個線程進(jìn)入
private void atomic4() {
AtomicIntegerFieldUpdater<T> tAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(T.class, "sum1");
T t = new T();
for (int i = 0; i < 10; i++) {
singleThreadPool.execute(() -> {
if (sum == 100) {
System.out.println(Thread.currentThread().getName() + " :" + "已修改");
}
});
}
System.out.println("=====");
for (int i = 0; i < 10; i++) {
singleThreadPool.execute(() -> {
if(tAtomicIntegerFieldUpdater.compareAndSet(t, 100, 120)){
System.out.print(Thread.currentThread().getName() + " :" + "tAtomicIntegerFieldUpdater 已修改");
}
});
}
singleThreadPool.shutdown();
}
原子引用類型
-
AtomicReferenceArray
保證引用數(shù)組的原子性 -
AtomicReferenceFieldUpdater
保證引用類型的字段更新 -
AtomicStampedReference
可以解決CAS
的ABA
問題,類似提供版本號 -
AtomicMarkableReference
可以解決CAS
的ABA
問題,提供是或否進(jìn)行判斷
原子累加器 JDK 1.8 新增
原有的
Atomic
系列類通過CAS
來保證并發(fā)時操作的原子性,但是高并發(fā)也就意味著CAS
的失敗次數(shù)會增多,失敗次數(shù)的增多會引起更多線程的重試,最后導(dǎo)致AtomicLong
的效率降低。新的四個類通過減少并發(fā),將單一value
的更新壓力分擔(dān)到多個value
中去,降低單個value
的“熱度”以提高高并發(fā)情況下的吞吐量
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
實(shí)例應(yīng)用
- 只貼了代碼片段。驗證累加器、整型、數(shù)組的原子性
package concurrent;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author 張博
*/
public class ThreadFactoryBuilder implements ThreadFactory {
private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
ThreadFactoryBuilder() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "pool-" + POOL_NUMBER.getAndIncrement() + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder();
private static ExecutorService singleThreadPool = new ThreadPoolExecutor(1000, 5000,
10, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
// 阻塞主線程,循環(huán)5000次后通知主線程關(guān)閉
private CountDownLatch begin = new CountDownLatch(5000);
// 模擬并發(fā)量一次200
private Semaphore semaphore = new Semaphore(200);
private static int count = 0;
private void atomic7() {
DoubleBinaryOperator doubleBinaryOperator = (x, y) -> x + y;
DoubleAccumulator doubleAccumulator = new DoubleAccumulator(doubleBinaryOperator, count);
AtomicInteger atomicInteger = new AtomicInteger(0);
int[] ints = new int[5000];
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5000);
for (int i = 0; i < 5000; i++) {
singleThreadPool.execute(() -> {
try {
// 允許200個線程進(jìn)入,模擬提供穩(wěn)定并發(fā)量
semaphore.acquire();
count();
atomicCount(doubleAccumulator);
atomicIntegerCount(atomicInteger);
array(ints);
atomicArray(atomicIntegerArray);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 每執(zhí)行一次減1
begin.countDown();
});
}
try {
// 沒到0一直等待,直到模擬并發(fā)結(jié)束
begin.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
singleThreadPool.shutdown();
System.out.println(count);
System.out.println(doubleAccumulator.get());
System.out.println(atomicInteger.get());
System.out.println(Arrays.toString(ints));
System.out.println("=========================================================================================================");
System.out.println(atomicIntegerArray.toString());
for (int i = 0; i < atomicIntegerArray.length(); i++) {
if ((atomicIntegerArray.get(i) - 5000) != 0) {
System.out.println(atomicIntegerArray.get(i));
}
}
}
/**
* 時間:2018/8/3 上午11:48
* @apiNote 線程不安全的累加
*/
private void count() {
count++;
}
/**
* 時間:2018/8/3 上午11:48
* @apiNote 原子累加
*/
private void atomicCount(DoubleAccumulator doubleAccumulator) {
doubleAccumulator.accumulate(1);
}
/**
* 時間:2018/8/3 上午11:48
* @apiNote 原子的i++
*/
private void atomicIntegerCount(AtomicInteger atomicInteger) {
atomicInteger.incrementAndGet();
}
/**
* 時間:2018/8/3 上午11:48
* @apiNote 線程不安全的數(shù)組操作
*/
private void array(int[] ints) {
for(int k = 0; k < 5000; k++) {
ints[k] += 1;
}
}
/**
* 時間:2018/8/3 上午11:48
* @apiNote 原子的數(shù)組操作
*/
private void atomicArray(AtomicIntegerArray atomicIntegerArray) {
for(int k = 0; k < 5000; k++) {
atomicIntegerArray.addAndGet(k, 1);
}
}
使用AtomicStampedReference
解決CAS
的ABA
問題
/**
* 時間:2018/8/3 上午11:58
* @apiNote 模擬并發(fā)導(dǎo)致 CAS 的 ABA 問題
*/
private void aba() {
// 原子引用類型
AtomicReference<String> stringAtomicReference = new AtomicReference<>("A");
// 原子時間戳引用
AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("A", 0);
// 線程1
singleThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("A", "B"));
System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("B", "A"));
System.out.println("=====");
});
// 線程2
singleThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " : -> stringAtomicReference " + stringAtomicReference.compareAndSet("A", "C"));
System.out.println("=====");
});
// 線程3
singleThreadPool.execute(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("A", "B", stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("B", "A", stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println("=====");
});
// 線程4
singleThreadPool.execute(() -> {
// 模擬并發(fā)時與線程3同時得到內(nèi)存中的A的時間戳
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " : 線程4 處理 cas 之前" + stamp);
// 線程4休眠2秒,模擬讓線程3已經(jīng)操作完成 A -> B -> A 的 CAS
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 得到線程3操作完的時間戳
System.out.println(Thread.currentThread().getName() + " :" + stampedReference.getStamp());
// 線程4進(jìn)行 A -> C 的 CAS 操作。這時會失敗。解決 ABA 問題
System.out.println(Thread.currentThread().getName() + " :" + stampedReference.compareAndSet("A", "C", stamp, stamp + 1));
});
singleThreadPool.shutdown();
}