摘要:方法可以將當(dāng)前線程放入等待集合中,并釋放當(dāng)前線程持有的鎖。此后,該線程不會接收到的調(diào)度,并進(jìn)入休眠狀態(tài)。該線程會喚醒,并嘗試恢復(fù)之前的狀態(tài)。
并發(fā)
最近重新復(fù)習(xí)了一邊并發(fā)的知識,發(fā)現(xiàn)自己之前對于并發(fā)的了解只是皮毛。這里總結(jié)以下Java并發(fā)需要掌握的點。
使用并發(fā)的一個重要原因是提高執(zhí)行效率。由于I/O等情況阻塞,單個任務(wù)并不能充分利用CPU時間。所以在單處理器的機器上也應(yīng)該使用并發(fā)。
為了實現(xiàn)并發(fā),操作系統(tǒng)層面提供了多進(jìn)程。但是進(jìn)程的數(shù)量和開銷都有限制,并且多個進(jìn)程之間的數(shù)據(jù)共享比較麻煩。另一種比較輕量的并發(fā)實現(xiàn)是使用線程,一個進(jìn)程可以包含多個線程。線程在進(jìn)程中沒有數(shù)量限制, 數(shù)據(jù)共享相對簡單。線程的支持跟語言是有關(guān)系的。Java 語言中支持多線程。
Java 中的多線程是搶占式的。這意味著一個任務(wù)隨時可能中斷并切換到其它任務(wù)。所以我們需要在代碼中足夠的謹(jǐn)慎,防范好這種切換帶來的副作用。
基礎(chǔ)Runnable 它可以理解成一個任務(wù)。它的run()方法就是任務(wù)的邏輯,執(zhí)行順序。
Thread 它是一個任務(wù)的載體,虛擬機通過它來分配任務(wù)執(zhí)行的時間片。
Thread中的start方法可以作為一個并發(fā)任務(wù)的入口。不通過start方法來執(zhí)行任務(wù),那么run方法就只是一個普通的方法
線程的狀態(tài)有四種:
NEW 線程創(chuàng)建的時候短暫的處于這種狀態(tài)。這種狀態(tài)下已經(jīng)可以獲得CPU時間了,隨后可能進(jìn)入RUNNABLE,BLOCKED狀態(tài)。
RUNNABLE 此狀態(tài)下只要CPU將時間分配給線程,線程中的任務(wù)就可以執(zhí)行。隨后可能進(jìn)入BLOCKED,DEAD狀態(tài)。
BLOCKED 線程可以運行,但是有某個條件阻止著它。當(dāng)線程處于阻塞狀態(tài)時,CPU不會分配時間片給它,直到它重新進(jìn)入RUNNABLE狀態(tài)。
DEAD 此狀態(tài)的線程將永遠(yuǎn)不會獲得CPU時間片。通常是因為run()方法返回才會到達(dá)此狀態(tài)。此時任務(wù)還是可以被中斷的。
Callable
Future
FutureTask
Java 1.5之后,不再推薦直接使用Thread對象作為任務(wù)的入口。推薦使用Executor管理Thread對象。Executor是線程與任務(wù)之間的的一個中間層,它屏蔽了線程的生命周期,不再需要顯式的管理線程。并且ThreadPoolExecutor 實現(xiàn)了此接口,我們可以通過它來利用線程池的優(yōu)點。
線程池涉及到的類有:Executor, ExecutorService, ThreadExecutorPool, Executors, FixedThreadPool, CachedThreadPool, SingleThreadPool。
Executor 只有一個方法,execute來提交一個任務(wù)
ExecutorService 提供了管理異步任務(wù)的方法,也可以產(chǎn)生一個Future對象來跟蹤一個異步任務(wù)。
主要的方法如下:
submit 可以提交一個任務(wù)
shutdown 可以拒絕接受新任務(wù)
shutdownNow 可以拒絕新任務(wù)并向正在執(zhí)行的任務(wù)發(fā)出中斷信號
invokeXXX 批量執(zhí)行任務(wù)
ThreadPoolExecutor 線程池的具體實現(xiàn)類。線程池的好處在于提高效率,能避免頻繁申請/回收線程帶來的開銷。
它的使用方法復(fù)雜一些,構(gòu)造線程池的可選參數(shù)有:
corePoolSize : int 工作的Worker的數(shù)量。
maximumPoolSize : int 線程池中持有的Worker的最大數(shù)量
keepAliveTime : long 當(dāng)超過Workder的數(shù)量corePoolSize的時候,如果沒有新的任務(wù)提交,超過corePoolSize的Worker的最長等待時間。超過這個時間之后,一部分Worker將被回收。
unit : TimeUnit keepAliveTime的單位
workQueue : BlockingQueue 緩存任務(wù)的隊列, 這個隊列只緩存提交的Runnable任務(wù)。
threadFactory : ThreadFactory 產(chǎn)生線程的“工廠”
handler : RejectedExecutionHandler 當(dāng)一個任務(wù)被提交的時候,如果所有Worker都在工作并且超過了緩存隊列的容量的時候。會交給這個Handler處理。Java 中提供了幾種默認(rèn)的實現(xiàn),AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy。
這里的Worker可以理解為一個線程。
這里之前想不通,覺得線程不可能重新利用綁定新任務(wù)。看了下源碼發(fā)現(xiàn)原來確實不是重新綁定任務(wù)。每一個Worker的核心部分只是一個循環(huán),不斷從緩存隊列中取任務(wù)執(zhí)行。這樣達(dá)到了重用的效果。
final void runWorker(Worker w) { Runnable task = w.firstTask; // ... try { while(task != null || (task=getTask())!=null) { try{ task.run(); } catch(Exception e){ } // ... } } finally { // ... } // ... }
Executors類提供了幾種默認(rèn)線程池的實現(xiàn)方式。
CachedThreadExecutor 工作線程的數(shù)量沒有上限(Integer的最大值), 有需要就創(chuàng)建新線程。
FixedThreadExecutor 預(yù)先一次分配固定數(shù)量的線程,之后不再需要創(chuàng)建新線程。
SingleThreadExecutor 只有一個線程的線程池。如果提交了多個任務(wù),那么這些人物將排隊,每個任務(wù)都在上一個人物執(zhí)行完之后執(zhí)行。所有任務(wù)都是按照它們的提交順序執(zhí)行的。
sleep(long) 當(dāng)前線程 中止 一段時間。它不會釋放鎖。Java1.5之后提供了更加靈活的版本。
TimeUnit 可以指定睡眠的時間單位。
優(yōu)先級 絕大多數(shù)情況下我們都應(yīng)該使用默認(rèn)的優(yōu)先級。不同的虛擬機中對應(yīng)的優(yōu)先級級別的總數(shù),一般用三個就可以了 MAX_PRIORITY, NORM_PRIORITY, MIN_PRIORITY。
讓步 Thread.yield()建議相同優(yōu)先級的其它線程先運行,但是不保證一定運行其它線程。
后臺線程 一個進(jìn)程中的所有非后臺線程都終止的時候整個進(jìn)程也就終止,同時殺死所有后臺線程。與優(yōu)先級沒有什么關(guān)系。
join() 線程 A 持有線程T,當(dāng)在線程T調(diào)用T.join()之后,A會阻塞,直到T的任務(wù)結(jié)束。可以加一個超時參數(shù),這樣在超時之后線程A可以放棄等待繼續(xù)執(zhí)行任務(wù)。
捕獲異常 不能跨線程捕獲異常。比如說不能在main線程中添加try-catch塊來捕獲其它線程中拋出的異常。每一個Thread對象都可以設(shè)置一個UncaughtExceptionHandler對象來處理本線程中拋出的異常。線程池中可以通過參數(shù)ThreadFactory來為每一個線程設(shè)置一個UncaughtExceptionHandler對象。
訪問共享資源在處理并發(fā)的時候,將變量設(shè)置為private非常的重要,這可以防止其它線程直接訪問變量。
synchronized 修飾方法在不加參數(shù)情況下,使用對象本身作為鎖。靜態(tài)方法使用Class對象作為鎖。同一個任務(wù)可以多次獲得對象鎖。
顯式鎖 Lock,相比synchronized更加靈活。但是需要的代碼更多,編寫出錯的可能性也更高。只有在解決特殊問題或者提高效率的時候才用它。
原子性 原子操作就是永遠(yuǎn)不會被線程切換中斷的操作。很多看似原子的操作都是非原子的,比如說long,double是由兩個byte表示的,它們的所有操作都是非原子的。所以,涉及到并發(fā)異常的地方都加上同步吧。除非你對虛擬機十分的了解。
volatile 這個關(guān)鍵字的作用在于防止多線程環(huán)境下讀取變量的臟數(shù)據(jù)。這個關(guān)鍵字在c語言中也有,作用是相同的。
原子類 AtomicXXX類,它們能夠保證對數(shù)據(jù)的操作是滿足原子性的。這些類可以用來優(yōu)化多線程的執(zhí)行效率,減少鎖的使用。然而,使用難度還是比較高的。
臨界區(qū) synchronized關(guān)鍵字的用法。不是修飾整個方法,而是修飾一個代碼塊。它的作用在于盡量利用并發(fā)的效率,減少同步控制的區(qū)域。
ThreadLocal 這個概念與同步的概念不同。它是給每一個線程都創(chuàng)建一個變量的副本,并保持副本之間相互獨立,互不干擾。所以各個線程操作自己的副本,不會產(chǎn)生沖突。
終結(jié)任務(wù)這里我講一下自己當(dāng)前的理解。
一個線程不是可以隨便中斷的。即使我們給線程設(shè)置了中斷狀態(tài),它也還是可以獲得CPU時間片的。只有因為sleep()方法而阻塞的線程可以立即收到InterruptedException異常,所以在sleep中斷任務(wù)的情況下可以直接使用try-catch跳出任務(wù)。其它情況下,均需要通過判斷線程狀態(tài)來判斷是否需要跳出任務(wù)(Thread.interrupted()方法)。
synchronized方法修飾的代碼不會在收到中斷信號后立即中斷。ReentrantLock鎖控制的同步代碼可以通過InterruptException中斷。
Thread.interrupted方法調(diào)用一次之后會立即清空中斷狀態(tài)。可以自己用變量保存狀態(tài)。
線程協(xié)作wait/notifyAll wait/notifyAll是Object類中的方法。調(diào)用wait/notifyAll方法的對象是互斥對象。因為Java中所有的Object都可以做互斥量(synchronized關(guān)鍵字的參數(shù)),所以wait/notify方法是在Object類中的。
wait與sleep 不同在于sleep方法是Thread類中的方法,調(diào)用它的時候不會釋放鎖;wait方法是Object類中的方法,調(diào)用它的時候會釋放鎖。
調(diào)用wait方法之前,當(dāng)前線程必須持有這段邏輯的鎖。否則會拋出異常,不能繼續(xù)執(zhí)行。
wait方法可以將當(dāng)前線程放入等待集合中,并釋放當(dāng)前線程持有的鎖。此后,該線程不會接收到CPU的調(diào)度,并進(jìn)入休眠狀態(tài)。有四種情況肯能打破這種狀態(tài):
有其它線程在此互斥對象上調(diào)用了notify方法,并且剛好選中了這個線程被喚醒;
有其它線程在此互斥對象上調(diào)用了notifyAll方法;
其它線程向此線程發(fā)出了中斷信號;
等待時間超過了參數(shù)設(shè)置的時間。
線程一旦被喚醒之后,它會像正常線程一樣等待之前持有的所有鎖。直到恢復(fù)到wait方法調(diào)用之前的狀態(tài)。
還有一種不常見的情況,spurious wakeup(虛假喚醒)。就是在沒有notify,notifyAll,interrupt的時候線程自動醒來。查了一些資料并沒有弄清楚是為什么。不過為了防止這種現(xiàn)象,我們要在wait的條件上加一層循環(huán)。
當(dāng)一個線程調(diào)用wait方法之后,其它線程調(diào)用該線程的interrupt方法。該線程會喚醒,并嘗試恢復(fù)之前的狀態(tài)。當(dāng)狀態(tài)恢復(fù)之后,該線程會拋出一個異常。
notify 喚醒一個等待此對象的線程。
notifyAll 喚醒所有等待此對象的線程。
當(dāng)兩個線程使用notify/wait或者notifyAll/wait進(jìn)行協(xié)作的時候,不恰當(dāng)?shù)氖褂盟鼈兛赡軙?dǎo)致一些信號丟失。例子:
T1: synchronized(shareMonitor){ // set up condition for T2 shareMonitor.notify(); } T2: while(someCondition){ // Point 1 synchronized(shareMonitor){ shareMonitor.wait(); } }
信號丟失是這樣發(fā)生的:
當(dāng)T2執(zhí)行到Point1的時候,線程調(diào)度器將工作線程從T2切換到T1。T1完成T2條件的設(shè)置工作之后,線程調(diào)度器將工作線程從T1切換回T2。雖然T2線程等待的條件已經(jīng)滿足,但還是會被掛起。
解決的方法比較簡單:
T2: synchronized(sharedMonitor) { while(someCondition) { sharedMonitor.wait(); } }
將競爭條件放到while循環(huán)的外面即可。在進(jìn)入while循環(huán)之后,在沒有調(diào)用wait方法釋放鎖之前,將不會進(jìn)入到T1線程造成信號丟失。
notify & notifyAll 前面已經(jīng)提過這兩個方法的區(qū)別。notify是隨機喚醒一個等待此鎖的線程,notifyAll是喚醒所有等待此鎖的線程。
Condition 他是concurrent類庫中顯式的掛起/喚醒任務(wù)的工具。它是真正的鎖(Lock)對象產(chǎn)生的一個對象。其實用法跟wait/notify是一致的。await掛起任務(wù),signalAll()喚醒任務(wù)。
生產(chǎn)者消費者隊列 Java中提供了一種非常簡便的容器,BlockingQueue。已經(jīng)幫你寫好了阻塞式的隊列。
除了BlockingQueue,使用PipedWriter/PipedReader也可以方便的在線程之間傳遞數(shù)據(jù)。
死鎖死鎖有四個必要條件,打破一個即可去除死鎖。
四個必要條件:
互斥條件。 互斥條件:一個資源每次只能被一個進(jìn)程使用。
請求與保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放。
不剝奪條件:線程已獲得的資源,在末使用完之前,不能強行剝奪。
循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
本來自己翻譯,但發(fā)現(xiàn)百度上描述的更好一些,直接copy到這里來,并把進(jìn)程換成了線程。
其它工具CountDownLatch 同步多個任務(wù),強制等待其它任務(wù)完成。它有兩個重要方法countDown,await以及構(gòu)造時傳入的參數(shù)SIZE。當(dāng)一個線程調(diào)用await方法的時候會掛起,直到該對象收到SIZE次countDown。一個對象只能使用一次。
CyclicBarrier 也是有一個SIZE參數(shù)。當(dāng)有SIZE個線程調(diào)用await的時候,全部線程都會被喚醒。可以理解為所有運動員就位后才能起跑,早就位的運動員只能掛起等待。它可以重復(fù)利用。
DelayQueue 一個無界的BlockingQueue,用來放置實現(xiàn)了Delay接口的對象,在隊列中的對象只有在到期之后才能被取走。如果沒有任何對象到期,就沒有頭元素。
PriorityBlockingQueue 一種自帶優(yōu)先級的阻塞式隊列。
ScheduledExecutor 可以把它想象成一種線程池式的Timer, TimerTask。
Semaphore 互斥鎖只允許一個線程訪問資源,但是Semaphore允許SIZE個線程同時訪問資源。
Exchanger 生產(chǎn)者消費者問題的特殊版。兩個線程可以在都‘準(zhǔn)備好了’之后交換一個對象的控制權(quán)。
ReadWriteLock 讀寫鎖。 讀-讀不互斥,讀-寫互斥,寫-寫互斥。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/64887.html
摘要:請參看前一篇文章并發(fā)學(xué)習(xí)筆記一原子性可見性有序性問題六等待通知機制什么是等待通知機制當(dāng)線程不滿足某個條件,則進(jìn)入等待狀態(tài)如果線程滿足要求的某個條件后,則通知等待的線程重新執(zhí)行。經(jīng)極客時間并發(fā)編程實戰(zhàn)專欄內(nèi)容學(xué)習(xí)整理 請參看前一篇文章:Java 并發(fā)學(xué)習(xí)筆記(一)——原子性、可見性、有序性問題 六、等待—通知機制 什么是等待通知—機制?當(dāng)線程不滿足某個條件,則進(jìn)入等待狀態(tài);如果線程滿足要...
摘要:最后,總結(jié)一下,導(dǎo)致并發(fā)問題的三個源頭分別是原子性一個線程在執(zhí)行的過程當(dāng)中不被中斷。可見性一個線程修改了共享變量,另一個線程能夠馬上看到,就叫做可見性。 計算機的 CPU、內(nèi)存、I/O 設(shè)備的速度一直存在較大的差異,依次是 CPU > 內(nèi)存 > I/O 設(shè)備,為了權(quán)衡這三者的速度差異,主要提出了三種解決辦法: CPU 增加了緩存,均衡和內(nèi)存的速度差異 發(fā)明了進(jìn)程、線程,分時復(fù)用 CP...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機制解讀抽象類與三大特征時間和時間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機制解讀抽象類與三大特征時間和時間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識點總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機制解讀抽象類與三大特征時間和時間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對象鎖和類鎖的區(qū)別,,優(yōu)缺點及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 2038·2023-04-26 01:33
閱讀 1666·2023-04-26 00:52
閱讀 1047·2021-11-18 13:14
閱讀 5454·2021-09-26 10:18
閱讀 2915·2021-09-22 15:52
閱讀 1495·2019-08-29 17:15
閱讀 3025·2019-08-29 16:11
閱讀 1044·2019-08-29 16:11