Java 多線程、Queue學(xué)習(xí),CAS學(xué)習(xí)

主題一:Queue:

Java并發(fā)(10)- 簡單聊聊JDK中的七大阻塞隊(duì)列
解讀 Java 并發(fā)隊(duì)列 BlockingQueue
Java多線程總結(jié)之線程安全隊(duì)列Queue
并發(fā)隊(duì)列ConcurrentLinkedQueue和阻塞隊(duì)列LinkedBlockingQueue用法
Java多線程總結(jié)之聊一聊Queue ---- 棒棒的!
Core Java 并發(fā):理解并發(fā)概念

  • 并行和并發(fā)區(qū)別:

1、并行是指兩者同時(shí)執(zhí)行一件事,比如賽跑,兩個(gè)人都在不停的往前跑;
2、并發(fā)是指資源有限的情況下,兩者交替輪流使用資源,比如一段路(單核CPU資源)同時(shí)只能過一個(gè)人,A走一段后,讓給B,B用完繼續(xù)給A ,交替使用,目的是提高效率

在Java多線程應(yīng)用中,隊(duì)列的使用率很高,多數(shù)生產(chǎn)消費(fèi)模型的首選數(shù)據(jù)結(jié)構(gòu)就是隊(duì)列(先進(jìn)先出)。
Java提供的線程安全的Queue可以分為阻塞隊(duì)列和非阻塞隊(duì)列。

阻塞隊(duì)列(同步隊(duì)列)--典型BlockingQueue: -- 線程安全

  • 1. ArrayBlockQueue:
    一個(gè)由數(shù)組支持的有界阻塞隊(duì)列。此隊(duì)列按 FIFO(先進(jìn)先出)原則對元素進(jìn)行排序。創(chuàng)建其對象必須明確大小,像數(shù)組一樣。

  • 2、LinkedBlockingQueue:
    由于LinkedBlockingQueue實(shí)現(xiàn)是線程安全的,實(shí)現(xiàn)了先進(jìn)先出等特性,是作為生產(chǎn)者消費(fèi)者的首選,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的話,默認(rèn)最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在隊(duì)列滿的時(shí)候會阻塞直到有隊(duì)列成員被消費(fèi),take方法在隊(duì)列空的時(shí)候會阻塞,直到有隊(duì)列成員被放進(jìn)來。
    LinkedBlockingQueue是一個(gè)線程安全的阻塞隊(duì)列,它實(shí)現(xiàn)了BlockingQueue接口,BlockingQueue接口繼承自java.util.Queue接口,并在這個(gè)接口的基礎(chǔ)上增加了take和put方法,這兩個(gè)方法正是隊(duì)列操作的阻塞版本。

  • 3. PriorityBlockingQueue:
    類似于LinkedBlockingQueue,但其所含對象的排序不是FIFO,而是依據(jù)對象的自然排序順序或者是構(gòu)造函數(shù)所帶的Comparator決定的順序。

  • 4. SynchronousQueue:
    同步隊(duì)列。同步隊(duì)列沒有任何容量,每個(gè)插入必須等待另一個(gè)線程移除,反之亦然。

非阻塞隊(duì)列(并發(fā)隊(duì)列)--典型ConcurrentLinkedQueue: -- 線程安全

  • 1、ConcurrentLinkedQueue:
    ConcurrentLinkedQueue,它是一個(gè)無鎖的并發(fā)線程安全的隊(duì)列,是Queue的一個(gè)安全實(shí)現(xiàn).Queue中元素按FIFO原則進(jìn)行排序.采用CAS操作,來保證元素的一致性。

主題二:線程:

實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:
線程里存在兩個(gè)隊(duì)列:
  • 阻塞隊(duì)列:wait、sleep、join
  • 就緒隊(duì)列:notify喚醒、wait時(shí)間過了、
線程的不安全體現(xiàn):
  • 內(nèi)存可見性
  • 操作原子性

Java并發(fā)編程:Thread類的使用
Synchronized 關(guān)鍵字使用、底層原理、JDK1.6 之后的底層優(yōu)化以及 和ReenTrantLock 的對比
一份針對于新手的多線程實(shí)踐
Java并發(fā)編程:同步容器
深入理解線程通信
java自帶線程池和隊(duì)列詳細(xì)講解

interrupt(線程中斷):

調(diào)用Thread的interrupt結(jié)束線程:

其實(shí)調(diào)用Thread對象的interrupt函數(shù)并不是立即中斷線程,只是將線程中斷狀態(tài)標(biāo)志設(shè)置為true,當(dāng)線程運(yùn)行中有調(diào)用其阻塞的函數(shù)(Thread.sleep,Object.wait,Thread.join等)時(shí),阻塞函數(shù)調(diào)用之后,會不斷地輪詢檢測中斷狀態(tài)標(biāo)志是否為true,如果為true,則停止阻塞并拋出InterruptedException異常,同時(shí)還會重置中斷狀態(tài)標(biāo)志;如果為false,則繼續(xù)阻塞,直到阻塞正常結(jié)束。

interrupt()方法有兩個(gè)作用:

  • 一個(gè)是將線程的中斷狀態(tài)置位(中斷狀態(tài)由false變成true);
  • 另一個(gè)則是讓被中斷的線程拋出InterruptedException異常。

這是很重要的。這樣,對于那些阻塞方法(比如 wait() 和 sleep())而言,當(dāng)另一個(gè)線程調(diào)用interrupt()中斷該線程時(shí),該線程會從阻塞狀態(tài)退出并且拋出中斷異常。這樣,我們就可以捕捉到中斷異常,并根據(jù)實(shí)際情況對該線程從阻塞方法中異常退出而進(jìn)行一些處理。

比如說:線程A獲得了鎖進(jìn)入了同步代碼塊中,但由于條件不足調(diào)用 wait() 方法阻塞了。這個(gè)時(shí)候,線程B執(zhí)行 threadA.interrupt()請求中斷線程A,此時(shí)線程A就會拋出InterruptedException,我們就可以在catch中捕獲到這個(gè)異常并進(jìn)行相應(yīng)處理(比如進(jìn)一步往上拋出)

關(guān)于Thread的靜態(tài)函數(shù)interrupted與Thread的對象函數(shù)isInterrupted:

  • 2函數(shù)都是調(diào)用了Native函數(shù)private native boolean isInterrupted(boolean ClearInterrupted);
  • 前者調(diào)用傳的參數(shù)為true,所以,調(diào)用interrupted函數(shù),會在檢測線程中斷狀態(tài)標(biāo)志是否為true后,還會將中斷狀態(tài)標(biāo)志重置為false。
  • 而isInterrupted函數(shù)只是檢測線程中斷狀態(tài)標(biāo)志。

如果線程在調(diào)用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法過程中受阻,則其中斷狀態(tài)將被清除,它還將收到一個(gè) InterruptedException。 我們可以捕獲該異常,并且做一些處理。
另外,Thread.interrupted()方法是一個(gè)靜態(tài)方法,它是判斷當(dāng)前線程的中斷狀態(tài),需要注意的是,線程的中斷狀態(tài)會由該方法清除。換句話說,如果連續(xù)兩次調(diào)用該方法,則第二次調(diào)用將返回 false(在第一次調(diào)用已清除了其中斷狀態(tài)之后,且第二次調(diào)用檢驗(yàn)完中斷狀態(tài)前,當(dāng)前線程再次中斷的情況除外)。

JAVA多線程之中斷機(jī)制(如何處理中斷?)

Sleep(線程睡眠):

Wait:

Join(線程合并):

線程合并是優(yōu)先執(zhí)行調(diào)用該方法的線程,再執(zhí)行當(dāng)前線程
所謂合并,就是等待其它線程執(zhí)行完,再執(zhí)行當(dāng)前線程,執(zhí)行起來的效果就好像把其它線程合并到當(dāng)前線程執(zhí)行一樣。

Yield(線程讓步):

線程讓步用于正在執(zhí)行的線程,在某些情況下讓出CPU資源,讓給其它線程執(zhí)行,來看一個(gè)小例子:


主題三:多線程:

Future、Callable、FutureTask:

Java并發(fā)編程:Callable、Future和FutureTask

  • Callable和Future,它倆很有意思的,一個(gè)產(chǎn)生結(jié)果,一個(gè)拿到結(jié)果。
  • Callable接口類似于Runnable,從名字就可以看出來了,但是Runnable不會返回結(jié)果,并且無法拋出返回結(jié)果的異常,
  • 而Callable功能更強(qiáng)大一些,被線程執(zhí)行后,可以返回值,這個(gè)返回值可以被Future拿到,也就是說,F(xiàn)uture可以拿到異步執(zhí)行任務(wù)的返回值
  • FutureTask實(shí)現(xiàn)了兩個(gè)接口,Runnable和Future,所以它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值,那么這個(gè)組合的使用有什么好處呢?假設(shè)有一個(gè)很耗時(shí)的返回值需要計(jì)算,并且這個(gè)返回值不是立刻需要的話,那么就可以使用這個(gè)組合,用另一個(gè)線程去計(jì)算返回值,而當(dāng)前線程在使用這個(gè)返回值之前可以做其它的操作,等到需要這個(gè)返回值時(shí),再通過Future得到。

Java進(jìn)階之FutureTask的用法及解析

Synchronized:

既保證了多線程的并發(fā)有序性,又保證了多線程的內(nèi)存可見性

  • synchronized底層的原理是跟jvm指令和monitor有關(guān)系的。

  • synchronized一般是對對象加鎖,對類加鎖也就是對類對象加鎖。如果使用了synchronized關(guān)鍵字,在底層編譯后的jvm指令中,會有monitorenter和monitorexit兩個(gè)指令。線程進(jìn)入synchronized代碼片段,執(zhí)行monitorenter指令對monitor計(jì)數(shù)器加1,這樣其他線程發(fā)現(xiàn)monitor的計(jì)數(shù)器不為0,就阻塞等待:


  • 線程出synchronized代碼片段,執(zhí)行monitorexit指令就是對monitor計(jì)數(shù)器減1,這樣其他線程發(fā)現(xiàn)monitor的計(jì)數(shù)器為0,就可以拿到鎖,給monitor的計(jì)數(shù)器加1,然后執(zhí)行業(yè)務(wù)邏輯了:


  • 上面的是針對synchronized對對象、類加鎖的底層原理。方法加鎖不是通過monitor指令,而是通過ACC_SYNCHORNIZED關(guān)鍵字,判斷方法是否同步。

Synchronized和Lock比較:

synchronized四種鎖狀態(tài)的升級

  • 鎖可以升級但不能降級:無鎖狀態(tài)->偏向鎖狀態(tài)->輕量級鎖狀態(tài)->重量級鎖狀態(tài)
  • 偏向鎖通過對比 Mark Word 解決加鎖問題,避免執(zhí)行CAS操作。
  • 輕量級鎖是通過用 CAS 操作和自旋來解決加鎖問題,避免線程阻塞和喚醒而影響性能。
  • 重量級鎖是將除了擁有鎖的線程以外的線程都阻塞。

synchronized四種鎖狀態(tài)的升級

synchronized 關(guān)鍵字原理
Java并發(fā)編程:synchronized

atomic(原子類包):

java.util.concurrent.atomic,該包是對Java部分?jǐn)?shù)據(jù)類型的原子封裝,在原有> 數(shù)據(jù)類型的基礎(chǔ)上,提供了原子性的操作方法,保證了線程安全
雖然使用CAS可以實(shí)現(xiàn)非阻塞式的原子性操作,但是會產(chǎn)生ABA問題

  • Compare and Swap, 翻譯成比較并交換。
  • CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。
  • java應(yīng)用:
    java.util.concurrent包中借助CAS實(shí)現(xiàn)了區(qū)別于synchronouse同步鎖的一種樂觀鎖。
    java.util.concurrent包完全建立在CAS之上的,沒有CAS就不會有此包。可見CAS的重要性。
  • ABA問題解決:
    JDK的atomic包里提供了一個(gè)類AtomicStampedReference來解決ABA問題。如果當(dāng)前引用 == 預(yù)期引用,并且當(dāng)前標(biāo)志等于預(yù)期標(biāo)志,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。

自旋鎖存在的問題:

  • 如果某個(gè)線程持有鎖的時(shí)間過長,就會導(dǎo)致其它等待獲取鎖的線程進(jìn)入循環(huán)等待,消耗CPU。使用不當(dāng)會造成CPU使用率極高。
  • 上面Java實(shí)現(xiàn)的自旋鎖不是公平的,即無法滿足等待時(shí)間最長的線程優(yōu)先獲取鎖。不公平的鎖就會存在“線程饑餓”問題。

自旋鎖的優(yōu)點(diǎn):

  • 自旋鎖不會使線程狀態(tài)發(fā)生切換,一直處于用戶態(tài),即線程一直都是active的;不會使線程進(jìn)入阻塞狀態(tài),減少了不必要的上下文切換,執(zhí)行速度快
  • 非自旋鎖在獲取不到鎖的時(shí)候會進(jìn)入阻塞狀態(tài),從而進(jìn)入內(nèi)核態(tài),當(dāng)獲取到鎖的時(shí)候需要從內(nèi)核態(tài)恢復(fù),需要線程上下文切換。 (線程被阻塞后便進(jìn)入內(nèi)核(Linux)調(diào)度狀態(tài),這個(gè)會導(dǎo)致系統(tǒng)在用戶態(tài)與內(nèi)核態(tài)之間來回切換,嚴(yán)重影響鎖的性能)

自旋鎖與互斥鎖:

  • 自旋鎖與互斥鎖都是為了實(shí)現(xiàn)保護(hù)資源共享的機(jī)制。
  • 無論是自旋鎖還是互斥鎖,在任意時(shí)刻,都最多只能有一個(gè)保持者。
  • 獲取互斥鎖的線程,如果鎖已經(jīng)被占用,則該線程將進(jìn)入睡眠狀態(tài);獲取自旋鎖的線程則不會睡眠,而是一直循環(huán)等待鎖釋放。

CAS原理分析
java的atomic包使用

Volatile:

  • volatile可以保證內(nèi)存可見性,不能保證并發(fā)有序性,既保證不了執(zhí)行的原子性
  • 防止指令重排

你應(yīng)該知道的 volatile 關(guān)鍵字
Java并發(fā)編程:volatile關(guān)鍵字解析

相比于synchroinized來說,volatile要輕量很多,執(zhí)行的成本會更低。原因是volatile不會引起線程上下文的切換和調(diào)度,但是它與synchronized的意義其實(shí)是有區(qū)別的。synchronized關(guān)鍵字主要體現(xiàn)的是互斥性。

ReentrantLock、Condition:

  • 線程互斥: 實(shí)現(xiàn)提供了比使用synchronized 方法和語句可獲得的更廣泛的鎖定操作,它能以更優(yōu)雅的方式處理線程同步問題。
  • 線程通信:
  • Condition將 Object 監(jiān)視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實(shí)現(xiàn)組合使用,為每個(gè)對象提供多個(gè)等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監(jiān)視器方法的使用
  • Condition是被綁定到Lock上的,要創(chuàng)建一個(gè)Lock的Condition必須用newCondition()方法。
  • Condition的強(qiáng)大之處在于它可以為多個(gè)線程間建立不同的Condition

ReentrantLock 實(shí)現(xiàn)原理
Java并發(fā)編程:Lock
Java線程(篇外篇):線程和鎖

  • AbstractQueuedSynchronized(AQS):抽象的隊(duì)列式的同步器

ThreadLocal:

Java并發(fā)編程:深入剖析ThreadLocal
ThreadLocal就是這么簡單

ThreadLocal內(nèi)存泄漏的根源是:

  • 由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應(yīng)key就會導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>

  • 想要避免內(nèi)存泄露就要手動remove()掉!

線程池:

  • FixedThreadPool:
    創(chuàng)建一個(gè)可重用固定線程集合的線程池,以共享的無界隊(duì)列方式來運(yùn)行這些線程。
    在FixedThreadPool中,有一個(gè)固定大小的池,如果當(dāng)前需要執(zhí)行的任務(wù)超過了池大小,那么多于的任務(wù)等待狀態(tài),直到有空閑下來的線程執(zhí)行任務(wù),而當(dāng)執(zhí)行的任務(wù)小于池大小,空閑的線程也不會去銷毀。
    ExecutorService threadPool = Executors.newFixedThreadPool(3);// 創(chuàng)建可以容納3個(gè)線程的線程池
  • CachedThreadPool:
    創(chuàng)建一個(gè)可根據(jù)需要創(chuàng)建新線程的線程池,但是在以前構(gòu)造的線程可用時(shí)將重用它們。
    CachedThreadPool會創(chuàng)建一個(gè)緩存區(qū),將初始化的線程緩存起來,如果線程有可用的,就使用之前創(chuàng)建好的線程,如果沒有可用的,就新創(chuàng)建線程,終止并且從緩存中移除已有60秒未被使用的線程。
    ExecutorService threadPool = Executors.newCachedThreadPool();// 線程池的大小會根據(jù)執(zhí)行的任務(wù)數(shù)動態(tài)分配
  • SingleThreadExecutor:
    創(chuàng)建一個(gè)使用單個(gè) worker 線程的 Executor,以無界隊(duì)列方式來運(yùn)行該線程。
    SingleThreadExecutor得到的是一個(gè)單個(gè)的線程,這個(gè)線程會保證你的任務(wù)執(zhí)行完成,如果當(dāng)前線程意外終止,會創(chuàng)建一個(gè)新線程繼續(xù)執(zhí)行任務(wù),這和我們直接創(chuàng)建線程不同,也和newFixedThreadPool(1)不同。
    ExecutorService threadPool = Executors.newSingleThreadExecutor();// 創(chuàng)建單個(gè)線程的線程池,如果當(dāng)前線程在執(zhí)行任務(wù)時(shí)突然中斷,則會創(chuàng)建一個(gè)新的線程替代它繼續(xù)執(zhí)行任務(wù)
  • ScheduledThreadPool:
    創(chuàng)建一個(gè)可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行的線程池。
    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);// 效果類似于Timer定時(shí)器

CountDownLatch、CyclicBarrier和Semaphore

Java并發(fā)編程:CountDownLatch、CyclicBarrier和Semaphore

1、CountDownLatch和CyclicBarrier都能夠?qū)崿F(xiàn)線程之間的等待,只不過它們側(cè)重點(diǎn)不同:
  1)、CountDownLatch一般用于某個(gè)線程A等待若干個(gè)其他線程執(zhí)行完任務(wù)之后,它才執(zhí)行;
  2)、而CyclicBarrier一般用于一組線程互相等待至某個(gè)狀態(tài),然后這一組線程再同時(shí)往下執(zhí)行;
  另外,CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。
2、Semaphore其實(shí)和鎖有點(diǎn)類似,它一般用于控制對某組資源的訪問權(quán)限。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容