程序是作業系統分配資源的最小單位,每個程序都是一個在執行中的程式,在windows中一個執行的xx.exe就是一個程序,他們都擁有自己獨立的一塊記憶體空間,一個程序可以有多個執行緒
執行緒是作業系統排程的最小單元,負責當前程序中程式的執行,一個程序可以執行多個執行緒,多個執行緒之間可以共用資料
管道是核心管理的一個緩衝區,相當於記憶體中一個的小紙條,管道的一端連線著一個程序的輸入,一端連線著另一個程序的輸出,當管道中沒有資訊的話,從管道中讀的程序會阻塞,直到另一端的程序放入資訊,當管道中資訊放滿時,嘗試放入資訊的程序會阻塞,直到另一個端程序取出資訊,當兩個程序都結束時,管道也就結束了
特點:單向的,一端輸入一端輸出,採用先進先出FIFO模式,大小4K,滿時寫阻塞,空時讀阻塞
分類:普通管道(僅父子程序間通訊)位於記憶體,命名管道位於檔案系統,沒有情緣關係的管道只要知道管道名也可以通訊
訊息佇列可以看做是一個訊息連結串列,只要執行緒有足夠的許可權,就可以往訊息佇列裡面存訊息和取訊息,他獨立於傳送程序和接收程序,提供了一種從一個程序向另一個程序傳送資料塊的方法,每一個資料塊都有一個訊息型別(頻道)和訊息內容(節目),每個型別相互不受影響,類似於一個獨立的管道
特點:全雙工可讀可寫,生命週期跟隨核心,每個資料塊都有一個型別,接收者可以有不同的型別值,每個訊息的最大長度是有上限的(MSGMAX),位元組數也是有上限的(MSGMNB),系統上的訊息佇列總數也是有上限的(MSGMNI),
號誌本質上是一個計數器,它不以傳送資料為目的,主要是用來保護共用資源,使得資源在一個時刻只有一個程序獨享
原理:號誌只有等待和傳送兩種操作,即P(sv)和V(sv)兩個操作都屬於原子操作
P(sv):如果sv的值大於0,就給它減1;如果它的值為0,就掛起該程序的執行
S(sv):如果有其他程序因等待sv而被掛起,就讓它恢復執行,如果沒有程序因sv而掛起,就給他加1
共用記憶體就是允許兩個程序或多個程序共用一定的儲存區,當一個程序改變了這個記憶體區域中的內容時,其他程序都會覺察到這個更改
特點:資料不需要在使用者端和伺服器端之間來回複製,資料直接寫到記憶體,減少了數次資料拷貝,是很快的一種IPC
缺點:共用記憶體沒有任何的同步和互斥機制,需要使用號誌來實現對共用記憶體的存取和同步
通訊端是一種允許兩個不同程序進行通訊的程式設計介面,通過通訊端介面,可以使一臺機器之間的程序可以相互通訊,也可以使不同機器上的程序進行網路通訊,通訊端明確把使用者端和伺服器端分開,實現了多個使用者端連線到一個伺服器端
原理:伺服器端應用程式呼叫socket建立一個通訊端,它是系統分配給伺服器程序的類似檔案描述符的資源,不能與其他程序共用,伺服器程序給這個通訊端起一個名字,本地通訊端的名字是Linux檔案系統中的檔名,一般在/tmp或/usr/tmp中,網路通訊端的名字與使用者端連線的特定網路有關的服務識別符號(埠號),系統呼叫bind給通訊端命名後,伺服器程序就開始等待使用者端連線到命名通訊端,系統呼叫一個listen建立一個佇列,用於存放來自使用者端的進入連線,伺服器用accept來接收使用者端的連線,當有使用者端連線時,伺服器程序會建立一個與原有命名不同的新的通訊端,這個通訊端只用於與這個特定的使用者端進行通訊,原有通訊端繼續處理來自其他使用者端的連線。
通訊端的域:
AF_INET域中的型別
執行緒之間共用程式的公共狀態,通過讀寫這個公共狀態來進行隱式通訊,這會經歷兩個過程
執行緒之間通過傳送訊息來顯示的進行通訊,比如wait()和notify()方法
執行緒被建立並啟動後,他既不是一啟動就進入執行狀態,也不是一直處於執行狀態,線上程的生命週期中,他要經過新建(new),就緒(Runnable),執行(Running),阻塞(Blocked)和死亡(Dead)5種狀態,在啟動過後,他不可能會一直佔用,所以狀態會在執行和阻塞之間切換
執行緒的上下文是指某一個時間點暫存器和程式計數器的內容,上下文切換可以認為是核心(作業系統的核心)在CPU上對於程序(包括執行緒)進行切換,上下文切換過程中的資訊是儲存在過程控制塊(PCB,process control block)的,PCB也被稱作為切換楨
上下文的切換過程
上下文切換的原因
暫存器是CPU內部數量較少但是速度很快的記憶體,
程式計數器是一個專用的暫存器,用於表明指令序列中CPU正在執行的位置,存的值為正在執行的指令的位置或者下一個將要被執行的指令的位置,具體依賴於特定的系統
執行緒排程器是一個作業系統服務,它負責為Runnable狀態的執行緒分配CPU時間片,當一個執行緒被建立和啟動後,它的執行便依賴於執行緒排程器,分配cpu時間可與基於執行緒的優先順序或者執行緒等待的時間
搶佔式排程指的是每條執行緒執行的時間,執行緒的切換都由系統控制,系統控制指的是在系統某種執行機制下,可能每條執行緒都分同樣的執行時間片,也可能是
協同式排程指某一個執行緒執行完成之後主動通知系統切換到一個執行緒上執行,這種模式就像接力賽一樣,一個接一個,執行緒的執行時間由執行緒本身控制,執行緒切換可以預知,不存在多執行緒同步的問題,缺點是如果一個執行緒編寫有問題,一直在執行中,那麼可能導致整個系統崩潰
按照任務進入佇列的順序,依次呼叫,執行完一個任務再執行下一個任務,只有當任務結束後才切換到下一個任務
優點:最小的工作切換開銷(沒有在任務執行中發生切換),最大的吞吐量(因為沒工作切換開銷),最樸實的公平性(先來先做)
缺點:平均響應時間高
適用場景:佇列中任務耗時差不多的場景
按照任務的耗時長短進行排程,優先排程耗時最短的任務,這個演演算法的前提是知道每個任務的耗時時間,需要注意的是耗時最短指的是剩餘執行時間,解決了先進先出演演算法中短耗時任務等待長耗時任務的窘境
優點:平均響應時間低
缺點:耗時長的任務遲遲得不到排程,不公平,容易形成飢餓,頻繁的工作切換,排程的額外開銷大
給佇列中的每個任務分配一個時間片,當時間片到了之後將此任務放到佇列的尾部,切換到下一個任務執行,解決了SJF中長耗時任務飢餓的問題
優點:每個任務都能夠得到公平的排程,耗時短的任務即使落在耗時長的任務後面,也能夠較快的得到排程執行
缺點:工作切換開銷大,需要多次切換任務上下文,時間片不好設定
適用場景:佇列中耗時差不多的任務
java使用的執行緒排程是搶佔式排程,java中執行緒會按優先順序分配cpu時間片,優先順序越高越先執行,但是優先順序高的執行緒並不能獨自佔用cpu時間片,只能是得到更多的cpu時間片,反之,優先順序低的執行緒分到的執行時間少,但不會分配不到執行時間
任何執行緒都可以設定為守護執行緒(Daemon)和使用者執行緒(User),預設情況下新建的執行緒是使用者執行緒,通過setDaemon(true)可以將執行緒設定為守護執行緒,這個函數必須線上程啟動前進行呼叫,否則會報錯,守護執行緒依賴於使用者執行緒,當用戶執行緒都退出了,守護執行緒也就退出了,典型的守護執行緒就是垃圾回收執行緒
執行緒安全是某個函數,函數庫在多執行緒的環境中被呼叫,能夠正確的處理多個執行緒之間的共用變數,不會對共用資源產生衝突,不會影響程式的執行結果
優勢:執行緒類只是實現了Runnable介面或者Callable介面,還可以繼承其他類,在這種方式下,多個執行緒可以共用同一個target物件,所以非常適合多個相同執行緒來處理同一份資源的情況,從而可以將CPU,程式碼和資料分開,形成清晰的模型,較好的體現了物件導向的思想
劣勢:程式設計稍微複雜,如果要存取當前執行緒,則必須Thread.currentThread()方法
優勢:編寫簡單,如果需要存取當前執行緒,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前執行緒
劣勢:繼承了Thread類,不能再繼承其他類
使執行緒進入waiting狀態,只有等待另外執行緒的通知或者被中斷才會返回,呼叫會釋放物件的鎖,因此wait方法一般用在同步方法或者同步程式碼塊中
使當前執行緒休眠,與wait方法不同的是sleep不會釋放當前鎖,會使執行緒進入timed-wating狀態
使當前執行緒從執行狀態變為就緒狀態,也就是當前執行緒讓出本次cpu時間片,與其他執行緒一起重新競爭CPU時間片
中斷一個執行緒,本質是給這個執行緒發行一個終止通知訊號,影響這個執行緒內部的一箇中斷標識位,這個執行緒本身並不會因此而改變狀態,注意線上程處於timed-wating狀態時呼叫interrupt會丟擲InterruptedException,使執行緒提前結束timed-wating狀態
中斷狀態是執行緒固有的一個標識位,通過isInterrupted來獲取標識位的值,根據值然後呼叫thread.interruptd方法來安全的終止執行緒
等待其他執行緒終止,在當前執行緒中呼叫一個執行緒的join()方法,則當前執行緒轉為阻塞狀態,回到另一個執行緒結束,當前執行緒再由阻塞狀態變為就緒狀態,等待cpu分配,適用於主執行緒啟動了子執行緒,需要用到子執行緒返回的結果,也就是主執行緒需要在子執行緒結束後再結束,這個時候就可以使用join方法
執行緒喚醒,喚醒在此物件監視器上等待的單個執行緒,如果所有執行緒都在此物件上等待,則會選擇喚醒其中的一個執行緒,選擇是任意的
isAlive():判斷一個執行緒是否存在
activeCount():獲取程式中活躍的執行緒數
enumerate():列舉程式中的執行緒
currentThread():得到當前執行緒
isDaemon():判斷當前執行緒是否為一個守護執行緒
setDaemon():設定一個執行緒為守護執行緒
setName():為一個執行緒設定一個名稱
setPriority():設定一個執行緒的優先順序
getPriority():獲得一個執行緒的優先順序
類不同:sleep方法屬於Thread類中,wait方法屬於Object類
釋放鎖:sleep不會釋放鎖,wait會釋放鎖,sleep不釋放鎖到指定時間就會自動喚醒,wait不會自動喚醒
應用場景不同:wait被用於通訊,sleep被用於暫停執行
start方法被用來啟動新建立的執行緒,內部呼叫了run方法,這和直接呼叫run方法效果不一樣,直接呼叫run方法的時候,只會是在原來的執行緒中呼叫,沒有新的執行緒啟動,start()方法會建立新的執行緒
notify只會喚醒一個執行緒,notiyfAll會喚醒所有執行緒
notify可能會導致死鎖,而notifyAll則不會,notify是對notifyAll的一個優化
interrupted會將中斷狀態清除,而isinterrupted不會,java多執行緒中的中斷機制使用這個內部識別符號來實現,當一箇中斷執行緒呼叫Thread.interrupt()來獲取中斷狀態時,中斷狀態會被清除,並設定為true,而非靜態方法呼叫isinterrupted用來查詢其他執行緒的中斷狀態不會改變中斷狀態的標識,任何丟擲InterruptedException異常的方法都會將中斷狀態清零
執行緒同步指執行緒之間的一種制約關係,一個執行緒的執行依賴另一個執行緒的訊息,當它沒有得到另一個執行緒的訊息時應等待,直到訊息到達時才被喚醒,執行緒的同步方法大體可以分為兩類,使用者模式和核心模式,核心模式指的是利用系統核心物件的單一性來進行同步,使用時需要切換核心態和使用者態,例如事件,號誌,互斥量,使用者模式不需要切換到核心態,例如原子操作(單一的一個全域性變數),臨界區
synchronized可以把任意一個非NULL的物件當做鎖,屬於獨佔式的悲觀鎖,同時也是可重入鎖
synchronized關鍵字是用來控制執行緒同步的,在多執行緒的環境下,控制synchronized程式碼段不被多個執行緒同時執行
volatile相比synchronized更加輕量
volatile修飾的變數具有synchronized的可見性,但是不具備原子性,也就是執行緒能夠自動發現volatile變數的最新值
volatile禁止了指令重排,在執行程式時,為了提升效能,處理器和編譯器常常會對指令進行重排,指令重排雖然不會影響單執行緒的執行結果,但是會破壞多執行緒的執行語意,指令重排有兩個條件,1在單執行緒環境下不能改變程式的執行結果,2存在資料依賴關係的情況不允許指令重排
適用場景:一個變數被多個執行緒共用,執行緒直接給這個變數賦值,例如狀態標記量和單例模式的雙檢鎖
ThreadLocal是一個本地執行緒副本變數工具類,主要用於將私有執行緒和該執行緒存放的副本物件做一個對映,各個執行緒之間的變數互不干擾,在高並行的場景下,可以實現無狀態的呼叫,特別適用於各個執行緒依賴不同變數值完成操作的場景,是一種空間換時間的做法,在每個Thread裡面維護了一個以開地址實現的ThreadLocal.ThreadLocalMap,把資料進行隔離,資料不共用,自然也就沒有執行緒安全的問題了
基本方法
應用場景:
特點
volatile應用在多個執行緒對範例變數更改的場合,重新整理主記憶體共用變數的值從而使得各個執行緒可以獲得最新的值,執行緒讀取變數的值需要從主記憶體中讀取;synchronized是鎖定當前變數,只有當前執行緒可以該變數,其他執行緒被阻塞住,synchronize會建立一個記憶體屏障,記憶體屏障保證了所有CPU操作結果都會直接刷到主記憶體中,從而保證了操作的記憶體可見性
volatile僅能使用在變數級別,synchronized則可以使用在變數,方法,和類級別
volatile不會造成執行緒的阻塞,synchronized會造成執行緒的阻塞
volatile只能保證變數的可見性不能保證原子性,synchronized保證了變數的可見性和原子性
volatile標記的變數不會編譯器優化,可以禁止指令重排,synchronized標記的變數可以被編譯器優化
悲觀鎖是一種悲觀思想,總是假設最壞的情況,即認為每次都是執行緒不安全的,每次讀寫都會進行加鎖,synchronized就是悲觀鎖的實現
樂觀鎖是一種樂觀思想,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,只是在更新的時候判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機制實現,適合於多讀的應用場景,在資料庫中write_condition機制就是樂觀鎖的實現,java中java.util.concurrent.atomic包下面的原子變數類也是使用了樂觀鎖的一種CAS實現方法
公平鎖加鎖前檢查是否有排隊等待的執行緒,優先排隊等待的執行緒,先來先得
非公平鎖加鎖時不考慮等待問題,直接嘗試獲取鎖,獲取不到則自動到隊尾等待
在相同條件下,非公平鎖的效能比公平鎖高5-10倍,因為公平鎖在多核的情況下需要維護一個佇列,增加了開銷
synchronized是非公平鎖,ReentrantLock預設的lock()方法採用的也是非公平鎖
java並行包提供的加鎖模式分為獨佔鎖和共用鎖
獨佔鎖模式下,每次只能有一個執行緒能持有鎖,ReentrantLock就是以獨佔方式實現的互斥鎖,獨佔鎖採用悲觀鎖的加鎖策略,避免了讀/讀寫衝突,如果某個唯讀執行緒獲取了鎖,則其他執行緒只能等待,這種情況其實不需要加鎖,因為讀操作並不會影響資料的一致性
共用鎖允許多個執行緒同時獲取鎖,並行存取共用資源,加鎖策略是樂觀鎖
可重入鎖指的是同一執行緒外層函數獲得鎖之後,內層遞迴函數仍然有獲取該鎖的程式碼,但不受影響,ReentrantLock和Synchronized都是可重入鎖
自旋鎖是指當一個執行緒在獲取鎖的時候,如果鎖已經被其他執行緒獲取,那麼該執行緒將回圈等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出迴圈,獲取鎖的執行緒一直處於活躍狀態
缺點:
優點:
鎖的狀態有四種:無鎖狀態,偏向鎖,輕量級鎖,重量級鎖
輕量級是相對於使用作業系統互斥量來實現的傳統鎖而言,輕量鎖不是用來代替重量級鎖的,他的本意是在沒有多執行緒競爭的前提下,減少傳統重量級鎖使用產生的效能消耗,適用於執行緒交替執行同步塊的情況,如果存在同一時間存取同一鎖的情況,就會導致輕量級鎖升級為重量級鎖
鎖升級
隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖,鎖的升級是單向的,只能從低到高,不會出現鎖降級
Synchronized是通過物件內部的一個叫做監視器鎖(monitor)來實現,但是監視器鎖本質是依賴於底層的作業系統的Mutex Lock來實現的,而作業系統實現執行緒之間的切換這就需要從使用者態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間 ,這也就Synchronized效率慢的原因,這依賴於作業系統Mutex Lock所實現的鎖稱之為重量級鎖,JDK1,6之後為了減少獲得鎖和釋放鎖所帶來的效能消耗,提高效能,引入了輕量級鎖和偏向鎖
偏向鎖的引入是為了在某個執行緒獲得鎖之後,消除這個執行緒的鎖重入(CAS)的開銷,看起來讓這個執行緒得到了偏向,減少不必要的輕量級鎖的執行路徑,因為輕量級鎖的獲取和釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,輕量級鎖是為了線上程交替執行同步塊時提高效能,而偏向鎖則是在只有一個執行緒執行同步塊時進一步提高效能
讀寫鎖分為讀鎖和寫鎖,讀鎖是無阻塞的,在沒有寫的情況下使用可以提高程式的效率,讀鎖和讀鎖不互斥,讀鎖和寫鎖互斥,寫鎖和寫鎖互斥
讀鎖適用於多個執行緒同時讀,但不能同時寫
寫鎖適用於只能一個執行緒寫資料,且其他執行緒不能讀取
分段鎖不是一種實際的鎖,而是一種思想,ConcurrenHashMap就是使用的分段鎖
Semaphore是一種基於計數的號誌,可以設定一個閾值,根據這個閾值,多個執行緒競爭獲取許可訊號,做完自己的申請後歸還,超過閾值後,申請許可號誌的執行緒將會阻塞,Semaphore可以用來構建一些物件池,資源池之類的,也可以建立一個計數為1的Semaphore二元號誌,類似互斥鎖的機制
CAS(Compare And Swap/Set)比較並交換,CAS是一種基於鎖的操作,CAS操作包含三個引數(V,A,B),記憶體位置(V),預期原值(A)和新值(B),如果記憶體地址裡面的值和A的值一樣,那麼就將記憶體裡面的值更新成B
CAS操作採用的是樂觀鎖,,通過無限迴圈來獲取鎖,如果在第一輪迴圈中,A執行緒的值被B執行緒修改了,那麼A執行緒需要自旋,到下次迴圈才有機會執行
缺點:
AQS的全稱是AbstractQueuedSynchronizer,這個類在java.util.concurrent.locks包下面,是一個用來構建鎖和同步器的框架
核心思想:如果被請求的共用資源空閒,則將當前請求資源的執行緒設定成有效的工作執行緒,並且將共用資源設定為鎖定狀態,如果被請求的共用資源被佔用,那麼就需要一套執行緒阻塞等待以及被喚醒時鎖分配的機制,這個機制AQS是用CLH佇列來實現的,即將暫時獲取不到的鎖加入到佇列中
CLH佇列是一個虛擬的雙向佇列,虛擬的雙向佇列即是一個不存在的佇列範例
基於AQS的實現:ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask
AQS使用一個int成員變數來表示同步狀態,通過內建的FIFO佇列來完成獲取資源執行緒的排隊工作,AQS使用CAS對該同步狀態進行原子操作實現對其值的修改
private volatile int state;//共用變數,使用volatile修飾保證執行緒的可見性
//獲取同步狀態的當前值
protected final int getState(){
return state;
}
//設定同步狀態的值
protected final void setState(int newState){
state = newState
}
//原子操作(CAS操作),將同步狀態值設定為給定值
protected final boolean compareAndSetState(int expect,int update){
return unsafe.compareAndSwapInt(this,stateOffset,expect,update);
}
AQS定義了兩種對資源的共用方式
不同的自定義同步器爭用共用資源的方式也不同,自定義同步器在實現時只需要實現共用資源state的獲取與釋放方式即可,至於具體執行緒等待佇列的維護(如獲取資源失敗時入隊和喚醒出隊等),AQS在頂層已經實現了
AQS使用了模板方法模式,自定義同步器時需要重寫下面幾個AQS提供的模板方法
isHeldExclusively()//該執行緒是否正在獨佔資源,只有用到condition才需要去實現它
tryAcquire(int) //獨佔方式,嘗試獲取資源,成功返回true,失敗返回false
tryRelease(int) //獨佔方式,嘗試釋放資源,成功返回true,失敗返回false
tryAcquireShared(int) //共用方式,嘗試獲取資源,負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源;
tryReleaseShared(int) //共用方式,嘗試釋放資源,成功則返回true,失敗返回false
預設情況下每個方法都丟擲UnsupportedIOperationException.這些方法的實現必須都是內部執行緒安全的。並且通常應該簡短而不是阻塞,AQS類中的其他方法都是final,所以無法被其他類使用,只有這幾個方法可以被其他類使用
一般來說,自定義同步器要麼是獨佔方式,要麼是共用方式,只需要實現tryAcquire-tryRelease或者tryAcquireShared-tryReleaseShared,但是AQS也支援同時實現兩種方式,比如ReentrantReadWriteLock
總結,synchronized關鍵字加到static靜態方法和程式碼塊上都是給Class類上鎖。加到實體方法就是給物件範例上加鎖,儘量不要使用到String型別上,因為JVM中字串常數池具有快取功能
public class SynchronizedDemo {
public void method(){
synchronized (this){
System.out.println("synchronized---code");
}
}
}
使用javap反編譯後 javap -c -v SynchronizedDemo
在執行方法之前之後都有一個monitorenter和monitorexit字元,前面的monitorenter就是獲取鎖,執行完程式碼後釋放鎖,執行monitorexit,第二個monitorexit是為了防止在同步程式碼塊中因異常退出而沒有釋放鎖的情況下,第二遍釋放,避免死鎖的情況
重入的原理是一個執行緒在獲取到該鎖之後,該執行緒可以繼續獲得鎖,底層原理維護了一個計數器,當執行緒獲得該鎖時,計數器加1,再次獲得該鎖時繼續加1,釋放鎖時,計數器減1,當計數器值為0時,表明該鎖未被任何執行緒所持有,其他執行緒可以競爭獲取鎖
在鎖物件的物件頭裡面有一個threadId欄位,在第一次存取的時候threadId為空,jvm讓其持有偏向鎖,並將threadid設定為執行緒id,再次進入的時候會先判斷threadid是否與其執行緒id一致,如果一致則可以繼續使用該物件,如果不一致則升級為了輕量級鎖,通過自旋迴圈一定次數之後,如果還沒有正常獲取到要使用的物件,此時就會把鎖再升級為重量級鎖,從而降低鎖帶來的效能消耗
Lock可以說是Synchronized的擴充套件版,Lock提供了無條件的,可輪訓的(tryLock方法),定時的(tryLock(long timeout,TimeUnit unit)),可中斷的(lockInterruptitibly),可多條件的(newCondition方法)鎖操作,另外Lock的實現類基本都支援非公平鎖和公平鎖,synchronized只支援非公平鎖
優勢:
主要方法
ReentrantLock繼承介面Lock並實現了定義的方法,是一種可重入鎖,除了能完成Synchronized所能完成的所有功能外,還提供了諸如可響應的中斷鎖,可輪詢鎖請求,定時鎖等避免死鎖的方法
使用ReentrantLock必須在finally中進行解鎖操作,避免程式出現異常而無法正常解鎖的情況
synchronized是悲觀鎖,屬於搶佔式,會引起其他執行緒阻塞
兩個都是可重入鎖
Synchronized是和if,else,for一樣的關鍵字,ReentrantLock是類
ReentrantLock相比synchronized的優勢是可中斷,公平鎖,多個鎖
減少鎖持有時間
只用在有執行緒安全要求的程式上加鎖
減小鎖粒度
將大物件(這個物件可能會被很多執行緒存取),拆成小物件,增加並行度,ConcurrentHashMap就是其中的一個實現
降低鎖競爭
使用偏向鎖,輕量級鎖,降低鎖競爭
鎖分離
根據功能將鎖分離開,一般分為讀鎖和寫鎖,做到讀讀不互斥,讀寫互斥,寫寫互斥,保證了執行緒安全,又提高了效能
鎖粗化
正常來說是為了保證執行緒間有效並行,會要求鎖的持有時間儘量短,但是如果時間太短,多個執行緒對一個鎖不停的請求,同步,釋放,這其中對鎖的開銷也會浪費系統資源
鎖消除
對不需要共用的資源中取消加鎖
容器類存放於Java.util包中,主要有3種:Set(集),list(列表,包含Queue)和Map(對映)
Set
List是有序的Collection,實現有ArrayList,Vector,LinkedList
底層通過陣列實現,允許對元素進行快速隨機存取,缺點是每個元素之間不能有間隔,當容量不夠需要增加容量時,需要將原來的資料複製到新的儲存中間中,當進行插入或者刪除時,需要對陣列進行復制,移動,代價比較高
適合隨機查尋和遍歷,不適合插入和刪除,列印時使用Arrays.toString()輸出每個元素
底層是通過陣列實現,是執行緒安全的,避免了多執行緒同時寫而引起的不一致性,效能比ArrayList慢
採用雙向連結串列結構儲存資料,很適合資料的動態插入和刪除,隨機存取和遍歷速度比較慢,提供了專門操作表頭和表尾的元素
set有獨一無二的性質,用於儲存無序(存入和取出)元素,值不能重複,資料是否重複的本質是比較物件的HashCode值,所以如果想要讓兩個物件相同,就必須覆蓋Object的hashCode和equals方法,實現有HashSet,TreeSet,LinkHashSet
內部採用HashMap實現,不允許重複的Key,只允許一儲存一個null物件,判斷key是否重複通過HashCode值來確定
使用二元樹的結構儲存資料,二元樹是有序的,排序時需要實現Comparable介面,重寫comoare函數,排序時該函數返回負整數,零,正整數分別對應小於,等於,大於
繼承於HashSet,實現了LinkedHashSet,底層採用LinkedHashMap來儲存元素
不是一個執行緒安全的容器,預設容量是16,根據鍵的hashCode儲存資料,大多數情況可以根據hash函數一次性獲取到資料,因此具有很快的查詢速度,鍵只允許一個null,值可以有多個null
JDK1.7採用陣列+連結串列的方式儲存,每次擴容都是2^n,擴容後是原來的兩倍,負載因子是0.75,擴容的閾值是當前陣列容量*負載因子
JDK1.8採用陣列+連結串列+紅黑樹方式儲存,相比JDK1.7多了一個紅黑樹,當連結串列的元素超過了8個以後會將連結串列轉換成紅黑樹
ConcurrentHashMap是一個執行緒安全的容器,底層是一個Segment陣列,預設長度是16,所以並行數是16,通過繼承ReentrantLock進行加鎖,每次加鎖的時候只鎖住陣列中的一個Segment,也就是分段鎖的思想,只要保證了操作的Segment執行緒安全,也就實現了全域性的執行緒安全
HashTable功能和HashMap相識,不同的是他屬於執行緒安全的,繼承自Dictionary,在效能上不如concurrentHashMap,使用場景較少,因為執行緒安全的時候使用concurrentHashMap,不需要保證執行緒安全的時候使用HashMap
TreeMap實現了SortedMap介面,底層資料結構是紅黑樹,儲存的時候根據鍵值升序排序,適用於在遍歷的時候需要得到的記錄是排序後的,注意在使用的時候,key必須實現Comparable介面,否則就會丟擲執行時異常java.lang.ClassCastException
LinkedHashMap是HashMap的一個子類,儲存了記錄的插入順序,在用iterator遍歷的時候,得到的記錄肯定是先插入的,可以在構造時帶引數,控制存取次序排序
使用場景
利用池化思想,將執行緒管理起來,使用的時候不需要再建立和銷燬,即用即拿提高了效率,減少了執行緒的開銷,方便了管理
剛建立時沒有一個執行緒,佇列有任務也不會執行
當呼叫execute時會根據當前執行緒數做出如下判斷
當執行緒池中的執行緒數量小於corePoolSize時則建立執行緒,並處理請求
當執行緒池中的執行緒數量大於等於corePoolSize時,則把請求放入workQueue中,隨著執行緒池中核心執行緒們不斷執行任務,只要執行緒池中有空閒的核心執行緒就從workQueue中獲取任務並執行
當workQueue已存滿時則新建非核心執行緒入池,並處理請求直到執行緒數量達到MaximumPoolSize(最大執行緒數量)
當執行緒池中執行緒數大於maximumPoolSize則使用相應的拒絕策略進行拒絕處理
當一個執行緒完成任務時,從佇列中取出下一個任務繼續執行
當一個執行緒無事可做,超過一定空閒時間時,執行緒池會做出判斷,如果當前執行緒數大於核心執行緒數,那麼這個執行緒就會被銷燬,所以當所有任務完成後,執行緒池最終會收縮到核心執行緒數的大小
總結:執行任務時檢查執行緒池中的執行緒數量,小於核心數則新建一個執行緒執行任務,大於等於核心數則放入任務佇列,大於核心數且小於最大執行緒數則建立新執行緒,當執行緒數大於核心執行緒數,且空閒時間超過了keepalive時則會銷燬執行緒
ThreadPoolExecutor.AbortPolicy
執行緒池預設的拒絕策略,當執行緒池中數量達到最大執行緒數時丟擲java.util.concurrent.RejectedExcutionException異常,任務不會被執行
ThreadPoolExecutor.DiscardPolicy
默默丟棄不能執行的新加任務,不會丟擲異常
ThreadPoolExcutor.CallerRunsPolicy
重試新增當前的任務,會自動重複呼叫execute()
ThreadPoolExecutor.DiscardOldestPolicy
拋棄執行緒池中工作佇列頭部的任務,也就是等待得最久的任務,並執行新傳入的任務
底層是通過new ThreadPoolExecutor(10,10,0L,TimeUnit.MILLSECONDS,new LinkedBlockingQueue())建立,初始化一個指定執行緒數的執行緒池,其中核心執行緒數和最大執行緒數相同,使用LinkedBlockingQueue作為阻塞佇列,當執行緒沒有可執行任務時不會釋放執行緒,由於使用LinkedBlockingQueue的特性,這個佇列是無界,若消費不過來,會導致記憶體被任務佇列佔滿,最終OOM
快取執行緒池,底層通過new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue())建立緩衝執行緒池,因為執行緒池的最大值是Integer.MAX_VALUE,所以當並行數很大,執行緒池來不及回收時,會導致嚴重的效能問題
佇列使用的LinkedBlockingQueue無界佇列,可以無限新增任務,直到記憶體溢位
submit可以返回持有計算結果的Future物件,execute返回型別是void
執行緒池大小要看執行什麼型別的任務,一般可分為CPU密集型,IO密集型
CPU密集型應該使用較小的執行緒池,一般為CPU核心數+1
IO密集型兩種方式 1:使用較大的執行緒池,一般為CPU核心數2,2:(執行緒等待時間與執行緒CPU時間之比+1)CPU核心數
CountDownLatch是基於AQS共用模式的實現,適用於一個或多個執行緒等待某個條件,期間阻塞,直到所有執行緒都符合,然後繼續執行的場景
構造時傳入一個int引數作為計數器,主要方法是countDown和await,每呼叫一次countDown()方法計數器減1,await方法會阻塞,直到計數器為0
CountDownLatch不能夠重用,計數器只能做減法,如果需要重用考慮使用CyclicBarrier或者重新建立CountDownLatch
CyclicBarrier中文叫柵欄,也可以叫同步屏障,讓一組執行緒都到達柵欄之前阻塞,當最後一個執行緒到達柵欄後放行,好比一扇門,預設是關閉狀態,阻塞執行緒的執行,直到所有執行緒都就位時,門才開啟,讓所有執行緒一起通過,最後還可以關閉,繼續下一輪
構造時傳入一個int引數作為需要攔截的執行緒數,每當一個執行緒呼叫await方法就會告訴CyclicBarrier已經有一個執行緒到達柵欄
許可證集合,用於限制可以存取某些資源的執行緒數目,這裡的限制指的是資源的互斥而不是同步,只能保證在同一時刻資源是互斥的,但不是同步的,這點和鎖不一樣
常用方法