以下爲自己學習多線程的時候總結的一些知識點
同時推薦一本很好的關於多線程高併發的書籍:《Java高併發編程詳解》
-
線程的生命週期:創建,就緒,運行,阻塞,死亡
新建狀態:當使用new關鍵字和Thread類或者子類建立了一個線程對象,該線程對象就處於新建狀態。它保持這個狀態直到程序start()這個線程。
就緒狀態:當線程對象調用了start()方法之後,該線程就進入了就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裏的線程調度器的調度。
運行狀態:如果就緒狀態的線程獲取CPU資源,就可以執行run(),此時線程便處於運行狀態。
阻塞狀態:如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔有的資源之後,該線程就從運行狀態進入阻塞狀態。
等待阻塞:運行狀態中的線程執行wait()方法,使線程進入等待阻塞狀態。
同步阻塞:線程在獲取sychronized同步鎖失敗。
其他阻塞:通過調用線程的sleep()或者join()發出了I/O請求,線程就進入了阻塞狀態。
死亡狀態:~~~~~~~ -
創建一個線程的方法:
繼承Thread類
實現Runnable接口
實現Callable接口與Future結合 -
推薦創建單線程的時候使用繼承Thread方式創建,創建多線程的時候使用Runnable、Callable接口來創建。
·繼承Thread類創建的線程,可以直接使用Thread類中的方法,比如使用sleep方法,而不必在前面加上一個Thread。
使用Thread類的侷限性:資源不能共享,無法放入線程池中等。
·使用Runnable、Callable接口的方式創建線程,可以實現資源共享,增強代碼的複用性,並且可以避免單繼承的侷限性,可以和線程池完美結合。 -
yield():表示暫停當前正在執行的線程,並執行其他線程。只是使當前線程回到可執行狀態,有可能進入可執行狀態後馬上又被執行
sleep():使當前線程進入停滯狀態,所以執行sleep()的線程在指定的時間內肯定不會被執行。 -
join(): 等待某個線程終止,才執行後面的代碼。
-
線程的優先級:setPriority()設置線程的優先級
Thread,MIN_PRIORITY(最低優先級、取值爲1)
Thread,MAX_PRIORITY(最高優先級、取值爲10)
Thread,NORM_PRIORITY(默認優先級、取值爲5) -
線程中常用的方法:
sleep():在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行);不會釋放對象鎖。
join():只等待t線程終止。
yield():暫停當前正在執行的線程對象,並執行其他線程。
setPriority():設置一個線程的優先級。
interrupt():調用當前線程的interrupt方法,可以打斷阻塞。
wait():強迫一個線程等待。它是一個Object的方法。wait方法會釋放對象鎖,讓其他線程可以訪問。
isAlive():判斷一個線程是否存活。
activeCount():程序中活躍的線程數。
currentThread():得到當前線程。
setDaemon():設置一個線程爲守護線程(用戶線程和守護線程的區別在於,是否等待主線程依賴於主線程結束而結束)。
setName():爲線程設置一個名稱。
notify():通知一個線程繼續運行,也是一個Object方法,通常和wait方法一起使用。 -
sleep()和wait()區別:
·使用sleep方法可以讓當前線程休眠,時間一到當前線程繼續往下執行,在任何地方都能使用,但需要捕獲InterruptedException異常。
·使用wait方法則必須放在synchronized塊裏面,同樣需要捕獲InterruptedException異常,並且需要獲取對象的鎖。
·wait方法還需要額外的方法notify/notifyAll進行喚醒,它們同樣需要放在synchronized塊裏面,且需要獲取對象的鎖。
使用場景:sleep一般對於當前線程休眠,或者輪循暫停操作,wait則多用於多線程之間的通信。
·sleep是Thread類的靜態本地方法,wait則是Object類的本地方法。
·sleep會讓出CPU執行時間且強制上下文切換,而wait則不一定,wait後可能還有機會重新競爭到鎖繼續執行。 -
並行:多個CPU實例或者多臺機器同時執行一段處理邏輯,是真正的同時。
併發:通過cpu調度算法。讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。 -
繼承Thread類的方法實現線程安全 static方法
-
兩個線程同時訪問一個對象的相同的synchronized方法
同一實例擁有同一把鎖,其他線程必然等待,順序執行 -
兩個線程同時訪問兩個對象的相同的synchronized方法
不同的實例擁有的鎖是不同的,所以不影響,並行執行 -
兩個線程同時訪問兩個對象的相同的static的synchronized方法
靜態同步方法,是類鎖,所有實例是同一把鎖,其他線程必然等待,順序執行 -
兩個線程同時訪問同一對象的synchronized方法與非synchronized方法
非synchronized方法不受影響,並行執行 -
兩個線程訪問同一對象的不同的synchronized方法
同一實例擁有同一把鎖,所以順序執行(說明:鎖的是this對象==同一把鎖) -
兩個線程同時訪問同一對象的static的synchronized方法與非static的synchronized方法
static同步方法是類鎖,非static是對象鎖,原理上是不同的鎖,所以不受影響,並行執行 -
方法拋出異常後,會釋放鎖嗎
會自動釋放鎖,這裏區別Lock,Lock需要顯示的釋放鎖 -
重寫Thread類的run方法和實現Runnable接口的run方法還有一個很重要的不同,Thread類的run方法不能共享,
而使用Runnable接口實現實現這一點。使用同一個Runnable的實例構造不同的Thread實例。 -
一個線程的創建肯定是由另一個線程完成的。 被創建的線程的父線程是創建他的線程。
-
main線程所在的ThreadGroup稱爲main;構造一個線程的時候如果沒有顯式地指定ThreadGroup,name它將會和父線程同屬於一個ThreadGroup
-
使用TimeUnit代替Thread.sleep
枚舉類TimeUnit對sleep方法提供了封裝,可以省去單位換算步驟
線程休眠3小時24分鐘14秒33毫秒可以表示爲:
TimeUnit.HOURS.sleep(3)
TimeUnit.MINUTES.sleep(24)
TimeUnit.SECONDS.sleep(14)
TimeUnit.MILLISECONDS.sleep(33) -
synchronized注意點:
·與monitor關聯的對象不能爲空
·synchronized作用域太大,由於synchronized關鍵字存在排他新,也就是說所有的線程必須串行的經過synchronized保護的共享區域
如果synchronized作用域越大,則代表着其效率越低,甚至還會喪失併發的優勢。
·this Monitor 對於同一實例有影響
·class Monitor與static synchronized 作用一樣 都是類鎖,對整個類有影響。 -
同步代碼的monitor必須與執行wait notify方法的對象一致,簡單地說就是用哪個對象的monitor進行同步,就只能用哪個對象進行wait和notify操作。
-
Java通過Executors提供四種線程池,分別爲:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。 -
RejectedExcecutionHandler線城市四種拒絕任務策略:
DiscardPolicy 直接丟棄
DiscardOldestPolicy 丟棄隊列中最老的任務
AbortPolicy 拋異常
CallerRunsPolicy 將任務分配給調用線程來執行 -
關閉線程池的兩個方法:
shutdown:線程池拒絕接受新提交的任務,同時立馬關閉線程池,線程池裏的任務不再執行。
shutdownNow: 線程池拒絕接收新提交的任務,同時立馬關閉線程池,線程池裏的任務不再執行。 -
餓漢式單例模式在多線程下可以保證只有一個實例,但是不能實現懶加載;
懶漢式單例模式在多線程下不能保證只有一個實例,但是可以採用在getInstance方法前加上static synchronized同步約束。 性能比較低下。