線程安全性
定義
當(dāng)多個(gè)線程訪問同一個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式,不論線程如何交替執(zhí)行,在主調(diào)代碼中不需要額外的協(xié)同或者同步代碼時(shí),這個(gè)類都可以表現(xiàn)出正確的行為,我們則稱這個(gè)類為線程安全的。
線程安全性
- 原子性:提供了互斥訪問,同一時(shí)刻只能有一個(gè)線程來(lái)對(duì)他進(jìn)行操作。
- 可見性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)被其他線程觀察到。
- 有序性:一個(gè)線程觀察其他線程中的指令順序,由于指令重排序的存在,該結(jié)果一般雜亂無(wú)序。
原子性 - Atomic包
- AtomicXXX 是通過 CAS(CompareAndSwap)來(lái)保證線程原子性 通過比較操作的對(duì)象的值(工作內(nèi)存的值)與底層的值(共享內(nèi)存中的值)對(duì)比是否相同來(lái)判斷是否進(jìn)行處理,如果不相同則重新獲取。如此循環(huán)操作,直至獲取到期望的值。
(關(guān)于什么是主內(nèi)存什么事工作內(nèi)存在上篇博客中進(jìn)行介紹了,不懂的同學(xué)可以翻一下)示例代碼:
@Slf4j
public class AtomicExample2 {
// 請(qǐng)求總數(shù)
public static int clientTotal = 5000;
// 同時(shí)并發(fā)執(zhí)行的線程數(shù)
public static int threadTotal = 200;
public static AtomicLong count = new AtomicLong(0);
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet();
// count.getAndIncrement();
}
}
- LongAdder和DoubleAdder
jdk8中新增的保證同步操作的類,我們之前介紹了AtomicXXX來(lái)保證原子性,那么為什么還有有LongAdder呢?
說(shuō)AtomicXXX的實(shí)現(xiàn)是通過死循環(huán)來(lái)判斷值的,在低并發(fā)的情況下AtomicXXX進(jìn)行更改值的命中率還是很高的。但是在高并發(fā)下進(jìn)行命中率可能沒有那么高,從而一直執(zhí)行循環(huán)操作,此時(shí)存在一定的性能消耗,在jvm中我們?cè)试S將64位的數(shù)值拆分成2個(gè)32位的數(shù)進(jìn)行儲(chǔ)存的,LongAdder的思想就是將熱點(diǎn)數(shù)據(jù)分離,將AtomicXXX中的核心數(shù)據(jù)分離,熱點(diǎn)數(shù)據(jù)會(huì)被分離成多個(gè)數(shù)組,每個(gè)數(shù)據(jù)都單獨(dú)維護(hù)各自的值,將單點(diǎn)的并行壓力發(fā)散到了各個(gè)節(jié)點(diǎn),這樣就提高了并行,在低并發(fā)的時(shí)候性能基本和AtomicXXX相同,在高并發(fā)時(shí)具有較好的性能,缺點(diǎn)是在并發(fā)更新時(shí)統(tǒng)計(jì)時(shí)可能會(huì)出現(xiàn)誤差。在低并發(fā),需要全局唯一,準(zhǔn)確的比如id等使用AtomicXXX,要求性能使用LongAdder
@Slf4j
public class AtomicExample3 {
// 請(qǐng)求總數(shù)
public static int clientTotal = 5000;
// 同時(shí)并發(fā)執(zhí)行的線程數(shù)
public static int threadTotal = 200;
public static LongAdder count = new LongAdder();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);、】【poiuytrewq;'
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count.increment();
}
}
- AtomicReference、AtomicReferenceFieldUpdater
AtomicReference是給定指定的期望值當(dāng)期望值與主內(nèi)存中的值相同然后更新,示例代碼
@Slf4j
public class AtomicExample4 {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0, 2); // 2
count.compareAndSet(0, 1); // no
count.compareAndSet(1, 3); // no
count.compareAndSet(2, 4); // 4
count.compareAndSet(3, 5); // no
log.info("count:{}", count.get());
}
}
AtomMNBVCXZenceFieldUpdater主要是更新某一個(gè)實(shí)例對(duì)象的一個(gè)字段這個(gè)字段必須是用volatile修飾同時(shí)不能是private修飾的,·157-=· 123444457890-
@Slf4j
public class AtomicExample5 {
private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
@Getter
public volatile int count = 100;
public static void main(String[] args) {
AtomicExample5 example5 = new AtomicExample5();
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 1, {}", example5.getCount());
}
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 2, {}", example5.getCount());
} else {
log.info("update failed, {}", example5.getCount());
}
}
}
最后我們介紹一下使用AtomicBoolean來(lái)實(shí)現(xiàn)只執(zhí)行一次的操作,我們使用private static AtomicBoolean isHappened = new AtomicBoolean(false)來(lái)初始化一個(gè)具有原子性的一個(gè)Boolean的記錄是否已經(jīng)被執(zhí)行
@Slf4j
public class AtomicExample6 {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
// 請(qǐng)求總數(shù)
public static int clientTotal = 5000;
// 同時(shí)并發(fā)執(zhí)行的線程數(shù)
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}", isHappened.get());
}
private static void test() {
if (isHappened.compareAndSet(false, true)) {
log.info("execute");
}
}
}
原子性 - 鎖
我們除了可以使用Atomic包還可以使用鎖來(lái)實(shí)現(xiàn)。
- synchronize:依賴jvm
- 修飾代碼塊:適用范圍大括號(hào)括起來(lái)的代碼,作用于調(diào)用的對(duì)象
- 修飾方法:適用范圍整個(gè)方法,作用于調(diào)用的對(duì)象
- 修飾靜態(tài)方法:適用范圍整個(gè)靜態(tài)方法,作用于所有對(duì)象
- 修飾一個(gè)類:適用范圍是括起來(lái)的部分,作用于所有對(duì)象
- Lock:依賴特殊的cpu指令、代碼實(shí)現(xiàn),ReentrantLock