摘要:線程安全性原子性提供了互斥訪問,同一時(shí)刻只能有一個(gè)線程來對(duì)他進(jìn)行操作。原子性包是通過來保證線程原子性通過比較操作的對(duì)象的值工作內(nèi)存的值與底層的值共享內(nèi)存中的值對(duì)比是否相同來判斷是否進(jìn)行處理,如果不相同則重新獲取。
線程安全性 定義
當(dāng)多個(gè)線程訪問同一個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式,不論線程如何交替執(zhí)行,在主調(diào)代碼中不需要額外的協(xié)同或者同步代碼時(shí),這個(gè)類都可以表現(xiàn)出正確的行為,我們則稱這個(gè)類為線程安全的。線程安全性
原子性:提供了互斥訪問,同一時(shí)刻只能有一個(gè)線程來對(duì)他進(jìn)行操作。
可見性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)被其他線程觀察到。
有序性:一個(gè)線程觀察其他線程中的指令順序,由于指令重排序的存在,該結(jié)果一般雜亂無序。
原子性 - Atomic包AtomicXXX 是通過 CAS(CompareAndSwap)來保證線程原子性 通過比較操作的對(duì)象的值(工作內(nèi)存的值)與底層的值(共享內(nèi)存中的值)對(duì)比是否相同來判斷是否進(jìn)行處理,如果不相同則重新獲取。如此循環(huán)操作,直至獲取到期望的值。
(關(guān)于什么是主內(nèi)存什么事工作內(nèi)存在上篇博客中進(jìn)行介紹了,不懂的同學(xué)可以翻一下)示例代碼:
@Slf4j public class AtomicExample2 { // 請求總數(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來保證原子性,那么為什么還有有LongAdder呢?
說AtomicXXX的實(shí)現(xiàn)是通過死循環(huán)來判斷值的,在低并發(fā)的情況下AtomicXXX進(jìn)行更改值的命中率還是很高的。但是在高并發(fā)下進(jìn)行命中率可能沒有那么高,從而一直執(zhí)行循環(huán)操作,此時(shí)存在一定的性能消耗,在jvm中我們允許將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ù)都多帶帶維護(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 { // 請求總數(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 AtomicReferencecount = 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 AtomicIntegerFieldUpdaterupdater = 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來實(shí)現(xiàn)只執(zhí)行一次的操作,我們使用private static AtomicBoolean isHappened = new AtomicBoolean(false)來初始化一個(gè)具有原子性的一個(gè)Boolean的記錄是否已經(jīng)被執(zhí)行
@Slf4j public class AtomicExample6 { private static AtomicBoolean isHappened = new AtomicBoolean(false); // 請求總數(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包還可以使用鎖來實(shí)現(xiàn)。
synchronize:依賴jvm
修飾代碼塊:適用范圍大括號(hào)括起來的代碼,作用于調(diào)用的對(duì)象
修飾方法:適用范圍整個(gè)方法,作用于調(diào)用的對(duì)象
修飾靜態(tài)方法:適用范圍整個(gè)靜態(tài)方法,作用于所有對(duì)象
修飾一個(gè)類:適用范圍是括起來的部分,作用于所有對(duì)象
Lock:依賴特殊的cpu指令、代碼實(shí)現(xiàn),ReentrantLock
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74433.html
摘要:是需要我們?nèi)ヌ幚砗芏嗍虑?,為了防止多線程給我們帶來的安全和性能的問題下面就來簡單總結(jié)一下我們需要哪些知識(shí)點(diǎn)來解決多線程遇到的問題。 前言 不小心就鴿了幾天沒有更新了,這個(gè)星期回家咯。在學(xué)校的日子要努力一點(diǎn)才行! 只有光頭才能變強(qiáng) 回顧前面: 多線程三分鐘就可以入個(gè)門了! Thread源碼剖析 本文章的知識(shí)主要參考《Java并發(fā)編程實(shí)戰(zhàn)》這本書的前4章,這本書的前4章都是講解并發(fā)的基...
摘要:將與當(dāng)前線程建立一對(duì)一關(guān)系的值移除。為了讓方法里的操作具有原子性,也就是在一個(gè)線程執(zhí)行這一系列操作的同時(shí)禁止其他線程執(zhí)行這些操作,提出了鎖的概念。 上頭一直在說以線程為基礎(chǔ)的并發(fā)編程的好處了,什么提高處理器利用率啦,簡化編程模型啦。但是磚家們還是認(rèn)為并發(fā)編程是程序開發(fā)中最不可捉摸、最詭異、最扯犢子、最麻煩、最惡心、最心煩、最容易出錯(cuò)、最不符合社會(huì)主義核心價(jià)值觀的一個(gè)部分~ 造成這么多最...
摘要:另一個(gè)是使用鎖的機(jī)制來處理線程之間的原子性。依賴于去實(shí)現(xiàn)鎖,因此在這個(gè)關(guān)鍵字作用對(duì)象的作用范圍內(nèi),都是同一時(shí)刻只能有一個(gè)線程對(duì)其進(jìn)行操作的。 線程安全性 定義:當(dāng)多個(gè)線程訪問某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行為,那么就稱這個(gè)類是線程安全的。 線程安全性主要體現(xiàn)在三個(gè)方面:原子性、可見性...
摘要:線程的這種交叉操作會(huì)導(dǎo)致線程不安全。原子操作是在多線程環(huán)境下避免數(shù)據(jù)不一致必須的手段。如果聲明一個(gè)域?yàn)橐恍┣闆r就可以確保多線程訪問到的變量是最新的。并發(fā)要求一個(gè)線程對(duì)對(duì)象進(jìn)行了操作,對(duì)象發(fā)生了變化,這種變化應(yīng)該對(duì)其他線程是可見的。 雖是讀書筆記,但是如轉(zhuǎn)載請注明出處 http://segmentfault.com/blog/exploring/ .. 拒絕伸手復(fù)制黨 一個(gè)問題: ...
摘要:的線程機(jī)制是搶占式。會(huì)讓出當(dāng)多個(gè)線程并發(fā)的對(duì)主存中的數(shù)據(jù)進(jìn)行操作時(shí),有且只有一個(gè)會(huì)成功,其余均失敗。和對(duì)象只有在困難的多線程問題中才是必須的。 并發(fā)簡述 并發(fā)通常是用于提高運(yùn)行在單處理器上的程序的性能。在單 CPU 機(jī)器上使用多任務(wù)的程序在任意時(shí)刻只在執(zhí)行一項(xiàng)工作。 并發(fā)編程使得一個(gè)程序可以被劃分為多個(gè)分離的、獨(dú)立的任務(wù)。一個(gè)線程就是在進(jìn)程中的一個(gè)單一的順序控制流。java的線程機(jī)制是...
閱讀 622·2021-11-18 13:12
閱讀 1344·2021-11-15 11:39
閱讀 2506·2021-09-23 11:22
閱讀 6247·2021-09-22 15:15
閱讀 3690·2021-09-02 09:54
閱讀 2344·2019-08-30 11:10
閱讀 3274·2019-08-29 14:13
閱讀 2932·2019-08-29 12:49