Java并發(fā)總結(jié)
1.多線程的優(yōu)點
- 資源利用率更好
- 程序在某些情況下更簡單
- 程序響應(yīng)更快
2.創(chuàng)建線程
1.實現(xiàn)Runnable接口
new Thread(Runnable).start()
- 可以避免由于java單繼承帶來的局限
- 增強程序的健壯性,代碼能夠被多個線程共享芦拿,代碼和數(shù)據(jù)是=數(shù)據(jù)是獨立的
- 適合多個相同程序代碼的線程區(qū)處理同意資源的情況
2.繼承Thread類
new MyThread().start()
注意:啟動線程的方式必須是start(),若是直接調(diào)用Thread.run()代碼也能執(zhí)行,但是就變成了普通方法的調(diào)用了酵幕,并沒有啟動線程
3.線程狀態(tài)
1.線程狀態(tài)介紹
線程在一定條件下芳撒,狀態(tài)會發(fā)生變化笔刹。線程一共有以下幾種狀態(tài):
新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于“可運行線程池”中,變得可運行,只等待獲取CPU的使用權(quán)。即在就緒狀態(tài)的進程除CPU之外,其它的運行所需資源都已全部獲得。
運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼箕昭。
阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán)述召,暫時停止運行锈津。直到線程進入就緒狀態(tài)性誉,才有機會轉(zhuǎn)到運行狀態(tài)煌往。阻塞的情況分三種:
等待阻塞:運行的線程執(zhí)行wait()方法曲管,該線程會釋放占用的所有資源,JVM會把該線程放入“等待池”中。進入這個狀態(tài)后,是不能自動喚醒的,必須依靠其他線程調(diào)用notify()或notifyAll()方法才能被喚醒,
同步阻塞:運行的線程在獲取對象的同步鎖時半沽,若該同步鎖被別的線程占用做葵,則JVM會把該線程放入“鎖池”中瘫筐。
其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時坦康、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。
死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
2.中斷機制
可中斷的阻塞狀態(tài):Thread.sleep(),Object.wait(),Thread.join(),ReenTrantLock.lockInterruptibly()。
不可中斷的阻塞狀態(tài):
synchronized和I/O阻塞狀態(tài)
1.可以通過調(diào)用Thread對象的interrupt()方法來中斷線程颓哮,但是此方法只是將中斷標(biāo)志設(shè)置為true標(biāo)志,并不能直接中斷線程,若執(zhí)行interrupt()方法時線程處于:
未阻塞狀態(tài),那么此時阻塞標(biāo)志已經(jīng)設(shè)為true忿偷,等到下一次阻塞狀態(tài)來臨時渠概,就會直接拋出InterruptedException異常筒狠,但是只會被捕捉到运挫,可以在catch塊內(nèi)自行return來結(jié)束run方法,否則,只是異常被捕捉,線程仍然可以繼續(xù)往下執(zhí)行
可中斷的阻塞狀態(tài)检激,線程收到中斷信號后,會立即拋出InterruptedException, 同時會把中斷狀態(tài)置回為false。
不可中斷的阻塞狀態(tài),不拋出InterruptedException,也不會退出阻塞狀態(tài)
2.檢查中斷狀態(tài):
使用 Thread對象的isInterrupted()方法判斷中斷狀態(tài),當(dāng)調(diào)用了interrupt(),isInterrupted()返回true妓雾,一旦拋出中斷異常中斷標(biāo)志被置為false机断,isInterrupted()返回false
Thread.interrupted()只是靜態(tài)方法奋蔚,只用來判斷當(dāng)前調(diào)用它的線程的中斷狀態(tài)馒过,和Thread對象的isInterrupted不同的是,它在每次調(diào)用一定會將中斷狀態(tài)置為false
4.守護線程
Java中有兩類線程:
1.用戶線程:運行在前臺的線程
2.守護線程:運行在后臺的線程蔼夜,并為前臺線程的運行提供便利服務(wù)(比如垃圾回收線程),當(dāng)所有的用戶線程都結(jié)束了,那么守護線程也會結(jié)束冷溃,因為被守護者沒有了凿歼。因此虐拓,不要在守護線程中執(zhí)行業(yè)務(wù)邏輯操作(比如對數(shù)據(jù)的讀寫等)。
- 可以用Thread對象的setDaemon(true)方法設(shè)置當(dāng)前線程為守護線程,但要在start()之前,否則無效.
- 在守護線程中創(chuàng)建的子線程也是守護線程
- 不是所有的應(yīng)用都可以分配給守護線程來進行服務(wù),比如讀寫操作或者計算邏輯
- 后臺進程在不執(zhí)行finally子句的情況下就會終止其run()方法
5.同步機制
1.原子性和可見性
原子性,不能被線程調(diào)度器中斷的操作,是不可分割的。由Java內(nèi)存模型來直接保證的原子性變量操作包括read隧甚、load菩帝、assign握础、use、store欠窒、和write這六個荐虐,基本數(shù)據(jù)類型的訪問讀寫是具備原子性的(long和double)的非原子性協(xié)定例外)贮乳,synchronized塊之間的操作也具備原子性
可見性刹缝,Java允許多個線程保存共享成員變量的私有拷貝,等到進行完操作后颂砸,再賦值回主存(減少了同主存通信的次數(shù),提高了運行的速度)碰缔。因此線程對變量的修改是互相不可見的腌且,在賦值的時候就會發(fā)生覆蓋柄粹,這樣就引出一個問題-變量可見性堪夭。因此恨豁,當(dāng)一個線程修改了共享變量的值计福,其他線程能夠立即得知這個修改抄瓦,就稱這個變量具有可見性埃叭。
2.volatile關(guān)鍵字
private volatile boolean value;
volatile關(guān)鍵字具有可見性,被它修飾的變量不能被線程拷貝,即直接在主存讀和寫类早,保證了新值能立即刷新涩僻,每個線程時刻看到的都是最新值逆日。因此室抽,保證了多線程操作時變量的可見性坪圾,而不具有原子性,因為它不會阻塞線程飘千,是一種稍弱的同步機制,要使volatile變量提供理想的線程安全(同時可見性和原子性)檐晕,必須同時滿足下面兩個條件凰萨,否則要加鎖來保證原子性:
- 對變量的寫操作不依賴于當(dāng)前值食棕。例如自增操作就依賴當(dāng)前值,因為它是一個讀取-修改-寫入操作序列組成的組合操作,必須以原子方式執(zhí)行蜡峰,而 volatile 不能提供必須的原子特性
- 該變量沒有包含在具有其他變量的不變式中。
3.synchronised關(guān)鍵字
采用synchronized修飾符實現(xiàn)的同步機制叫做互斥鎖機制搀玖,每一個對象都有一個monitor(鎖標(biāo)記)挎袜,只能分配給一個線程娶牌。當(dāng)線程擁有這個鎖標(biāo)記時才能訪問這個資源,沒有鎖標(biāo)記便進入鎖池鹦马,因此叫做互斥鎖和悦,synchronised同時具有可見性和原子性舵匾,原子性是因為鎖內(nèi)的操作不可分割钱贯,可見性因為 入鎖(進入synchronized)會獲取主存中變量的最新值和出鎖(退出synchronized)會將變量的最新值刷新回主存
1.實例方法的同步與實例方法內(nèi)的同步塊
實例方法內(nèi)的synchronized是同步在某個實例對象上。即對象鎖
public class MyClass {
public synchronized void log1(String msg1, String msg2){
//...
}
public void log2(String msg1, String msg2){
synchronized(object){
//...
}
}}
2.靜態(tài)方法的同步與靜態(tài)方法內(nèi)的同步塊
靜態(tài)方法內(nèi)的synchronized是同步在類對象上叹坦,鎖住的是整個類齿椅,即類鎖
public class MyClass {
public static synchronized void log1(String msg1, String msg2){
//...
}
public void log2(String msg1, String msg2){
synchronized(MyClass.class){
//...
}
}
}
使用同步機制獲取互斥鎖的情況,進行幾點說明:
一旦某個鎖被某個線程獲取费尽,那么其他所有在這個鎖(同一個對象或類)上競爭的線程都會阻塞,不管是不是同一個方法或代碼塊(仔細體會)敌蚜。以對象鎖為例贫奠,假如有三個synchronized方法a拌汇,b净响,c仿滔,當(dāng)線程A進入實例對象M中的方法a時,它便獲得了該M對象鎖孝情,其他的線程會在M的所有的synchronized方法處阻塞,即在方法 a找颓,b击狮,c 處都要阻塞
對象級別鎖彪蓬,鎖住的是對象膘茎,有上面說的鎖的特性.
類級別鎖,鎖住的是整個類捣郊,它用于控制對 static 成員變量以及 static 方法的并發(fā)訪問呛牲。有上面說的鎖的特性
互斥是實現(xiàn)同步的一種手段娘扩,臨界區(qū)琐旁、互斥量和信號量都是主要的互斥實現(xiàn)方式灰殴。synchronized 關(guān)鍵字經(jīng)過編譯后牺陶,會在同步塊的前后分別形成 monitorenter 和 monitorexit這兩個字節(jié)碼指令掰伸。根據(jù)虛擬機規(guī)范的要求皱炉,在執(zhí)行 monitorenter指令時,首先要嘗試獲取對象的鎖狮鸭,如果獲得了鎖合搅,把鎖的計數(shù)器加 1,相應(yīng)地歧蕉,在執(zhí)行 monitorexit 指令時會將鎖計數(shù)器減 1,當(dāng)計數(shù)器為 0 時惯退,鎖便被釋放了赌髓。由于synchronized同步塊對同一個線程是可重入的,一個線程可以多次獲得同一個對象的互斥鎖,要釋放相應(yīng)次數(shù)的該互斥鎖春弥,才能最終釋放掉該鎖。
4.顯示的Lock鎖
unlock()需放在finally子句中叠荠,try中必須有return匿沛,以確保unlock()不會過早的發(fā)生。
顯示的Lock對象在加鎖和釋放鎖方面榛鼎,相比synchronized逃呼,還賦予了更細粒度的控制力。
Lock對象必須被顯示的創(chuàng)建者娱、鎖定和釋放抡笼。相比synchronized,代碼缺乏優(yōu)雅性黄鳍。
在使用Lock鎖時推姻,某些事物失敗拋出一個異常,可以使用finally去做清理工作框沟,以維護系統(tǒng)使其處于良好的狀態(tài)藏古,這是synchronized不具有的
ReentrantLock允許嘗試獲取但最終未獲取鎖,如果其他線程已經(jīng)獲取這個鎖忍燥,可以決定離開去執(zhí)行其他一些事情拧晕,而不是等待鎖被釋放。這是synchronized不具有的
5.synchronized 和 Volatile的比較
在訪問volatile變量時不會執(zhí)行加鎖操作梅垄,因此也不會使線程阻塞厂捞。
加鎖機制既可以確保可見性又可以確保原子性队丝,而 volatile 變量只能確泵夷伲可見性。
如果過度依賴volatile變量來控制狀態(tài)的可見性炭玫,通常會比使用鎖的代碼更脆弱奈嘿。僅當(dāng)volatile變量能簡化代碼的實現(xiàn)以及對同步策略的驗證時,才使用它
在需要同步的時候吞加,第一選擇應(yīng)該是synchronized關(guān)鍵字裙犹,這是最安全的方式,
6.TheadLocal
ThreadLocal類的實例衔憨,即便被多個線程鎖共享叶圃,但是在每個線程當(dāng)中都有一份私有拷貝,并且多個線程無法看到對方的值践图,即線程對于此變量的使用完全是在自己拷貝對象上掺冠。
private ThreadLocal myThreadLocal = new ThreadLocal();
ThreadLocal可以儲存任意對象
myThreadLocal.set("A thread local value");//存儲此對象的值
String threadLocalValue = (String) myThreadLocal.get();//讀取
ThreadLocal myThreadLocal1 = new ThreadLocal<String>();//泛型使用
7.死鎖
1.普通循環(huán)等待死鎖
如果在同一時間,線程A持有鎖M并且想獲得鎖N,線程B持有鎖N并且想獲得鎖M德崭,那么這兩個線程將永遠等待下去斥黑,這種情況就是最簡單的死鎖形式。
public class DeadLock{
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() throws Exception
{
synchronized (left)
{
Thread.sleep(2000);
synchronized (right)
{
System.out.println("leftRight end!");
}
}
}
public void rightLeft() throws Exception
{
synchronized (right)
{
Thread.sleep(2000);
synchronized (left)
{
System.out.println("rightLeft end!");
}
}
}
}
死鎖的四個必要條件:
- 互斥條件:線程對所分配到的資源進行排他性使用眉厨,即在一段時間內(nèi)锌奴,某資源只能被一個線程占用。如果此時還有其它線程請求該資源憾股,則只能等待鹿蜀,直到占有該資源的線程用畢釋放。
- 請求和保持條件:已經(jīng)保持了至少一個資源服球,但是又提出新的資源請求茴恰,而資源已被其他線程占有,此時請求線程只能等待斩熊,但對自己已獲得的資源保持不放往枣。
- 不可搶占條件:線程已獲得的資源在未使用完之前不能被搶占,只能線程使用完之后自己釋放粉渠。
- 循環(huán)等待條件:在發(fā)生死鎖時婉商,一個任務(wù)等待其他任務(wù)所持有的資源,后者又在等待另一個任務(wù)所持有的資源渣叛,這樣一直下去丈秩,直到有一個任務(wù)所持有的資源,使得大家被鎖住淳衙。
避免死鎖的方式:
1蘑秽、只在必要的最短時間內(nèi)持有鎖,考慮使用同步語句塊代替整個同步方法箫攀;
2肠牲、設(shè)計時考慮清楚鎖的順序,盡量減少潛在的加鎖交互數(shù)量
3靴跛、既然死鎖的產(chǎn)生是兩個線程無限等待對方持有的鎖缀雳,我們可以使用Lock類中的tryLock方法去嘗試獲取鎖,這個方法可以指定一個超時時限梢睛,獲取鎖超時后會返回一個失敗信息肥印,放棄取鎖。
2.重入鎖死
如果一個線程在兩次調(diào)用lock()間沒有調(diào)用unlock()方法绝葡,那么第二次調(diào)用lock()就會被阻塞深碱,這就出現(xiàn)了重入鎖死。避免重入鎖死有兩個選擇:
- 編寫代碼時避免再次獲取已經(jīng)持有的鎖
- 使用可重入鎖
8.多線程集合的安全使用
在 Collections 類中有多個靜態(tài)方法藏畅,它們可以獲取通過同步方法封裝非同步集合而得到的集合:
public static Collection synchronizedCollention(Collection c)
public static List synchronizedList(list l)
public static Map synchronizedMap(Map m)
public static Set synchronizedSet(Set s)
public static SortedMap synchronizedSortedMap(SortedMap sm)
public static SortedSet synchronizedSortedSet(SortedSet ss)
在多線程環(huán)境中敷硅,當(dāng)遍歷當(dāng)前集合中的元素時,希望阻止其他線程添加或刪除元素。安全遍歷的實現(xiàn)方法如下:
import java.util.*;
public class SafeCollectionIteration extends Object {
public static void main(String[] args) {
//為了安全起見绞蹦,僅使用同步列表的一個引用力奋,這樣可以確保控制了所有訪問
//集合必須同步化幽七,這里是一個List
List wordList = Collections.synchronizedList(newArrayList());
//wordList中的add方法是同步方法刊侯,會自動獲取wordList實例的對象鎖
wordList.add("Iterators");
wordList.add("require");
wordList.add("special");
wordList.add("handling");
//獲取wordList實例的對象鎖,
//迭代時锉走,此時必須阻塞其他線程調(diào)用add或remove等方法修改元素
synchronized ( wordList ) {
Iterator iter = wordList.iterator();
while ( iter.hasNext() ) {
String s = (String) iter.next();
System.out.println("found string: " + s + ", length=" + s.length());
}
}
}
}
大部分的線程安全類都是相對線程安全的,也就是我們通常意義上所說的線程安全藕届,像Vector這種挪蹭,add、remove方法都是原子操作休偶,不會被打斷梁厉,但也僅限于此,如果有個線程在遍歷某個Vector踏兜、有個線程同時在add這個Vector词顾,99%的情況下都會出現(xiàn)·ConcurrentModificationException·,也就是fail-fast機制
6.多線程協(xié)作
1.wait碱妆、notify肉盹、notifyAll的使用
wait():將當(dāng)前線程置入休眠狀態(tài),直到接到喚醒通知或被中斷為止疹尾。調(diào)用后當(dāng)前線程立即釋放鎖上忍。
notify():用來通知那些正在等待該對象的對象鎖的其他線程。如果有多個線程等待纳本,則線程規(guī)劃器任意挑選出其中一個wait()狀態(tài)的線程來發(fā)出通知喚醒它窍蓝。其他線程繼續(xù)阻塞,但是調(diào)用后當(dāng)前線程不會立馬釋放該對象鎖繁成,直到程序出鎖
notifyAll():notifyAll會使在該對象鎖上wait的所有線程統(tǒng)統(tǒng)退出wait的狀態(tài)(即全部被喚醒),待程序出鎖后吓笙,所有被喚醒的線程共同競爭該鎖,沒有競爭到鎖的線程會一直競爭(不是阻塞)
注意:在調(diào)用wait巾腕、notify面睛、notifyAll方法之前,必須先獲得對象鎖尊搬,并且只能在synchronized代碼塊或者方法中調(diào)用侮穿。否則拋出IllegalMonitorStateException異常
總結(jié):
如果線程調(diào)用了對象的wait()方法,那么該線程便會處于該對象的 等待池 中毁嗦,等待池中的線程不會去競爭該對象的鎖亲茅。
如果線程調(diào)用了對象的notifyAll()方法(喚醒所有wait線程)或notify()方法(只隨機喚醒一個wait線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖克锣。
優(yōu)先級高的線程競爭到對象鎖的概率大茵肃,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中袭祟。
2.notify 通知的遺漏
當(dāng)線程 A 還沒開始 wait 的時候验残,線程 B 已經(jīng) notify 了,這樣巾乳,線程 B 的通知是沒有任何響應(yīng)的您没,當(dāng) 線程B 退出 synchronized 代碼塊后,線程A 再開始 wait胆绊,便會一直阻塞等待氨鹏。也就是說這個通知信號提前來了,沒有wait線程收到压状,因此丟失了信號仆抵,為了避免丟失信號,可以設(shè)置一個成員變量來標(biāo)志信號种冬。
public class MyWaitNotify{
MonitorObject myMonitorObject = new MonitorObject();
//一旦調(diào)用notify镣丑,則設(shè)為true表示喚醒信號發(fā)出來了,則設(shè)為false表示喚醒信號已經(jīng)被其他某個線程消耗了娱两,
boolean wasSignalled = false;
public void doWait(){
synchronized(myMonitorObject){
//自旋鎖,循環(huán)檢查莺匠,只有當(dāng)為true時才表示有喚醒信號來了
while(!wasSignalled){
try{
myMonitorObject.wait();
} catch(InterruptedException e){...}
}
//clear signal and continue running.
wasSignalled = false;
}
}
public void doNotify(){
synchronized(myMonitorObject){
wasSignalled = true;
myMonitorObject.notify();
}
}
}
如果有多個線程被notifyAll()喚醒,所有被喚醒的線程都會在while循環(huán)里檢查wasSignalled變量值十兢,但是只有一個線程可以獲得對象鎖并且退出wait()方法并清除wasSignalled標(biāo)志(設(shè)為false)慨蛙。這時這個標(biāo)志已經(jīng)被第一個喚醒的線程消耗了,所以其余的線程會檢查到標(biāo)志為false纪挎,還是會回到等待狀態(tài)期贫。
3.字符串常量或全局對象作為鎖的隱患
字符串常量和全局對象在不同的實例當(dāng)中是同一個對象,即其實用的是同一把鎖异袄。因此本來在不同實例對象的線程會互相干擾通砍,例如在實例A中的線程調(diào)用notifyAll()可能會喚醒實例B當(dāng)中的wait線程。因此應(yīng)該避免使用這兩種對象作為監(jiān)視器對象烤蜕,而應(yīng)使用每個實例中唯一的對象
String myMonitorObject = "";//相同String 常量賦值在內(nèi)存當(dāng)中只會有一份對象
4.生產(chǎn)者-消費者模型synchronized實現(xiàn)
生產(chǎn)者和消費者在同一時間段內(nèi)共用同一存儲空間封孙,生產(chǎn)者向空間里生產(chǎn)數(shù)據(jù),而消費者取走數(shù)據(jù)讽营。問題在于如何通過線程之間的協(xié)作使得生產(chǎn)和消費輪流進行虎忌。
package job_3;
import java.util.Random;
public class Datebuf {
private Integer i = 0;
private int result;
private Random random;
public Datebuf() {
random = new Random();
}
public void sendData() {
while (!Thread.interrupted()) {
synchronized (this) {
try {
while (i != 0) {
this.wait();
}
i = random.nextInt(100);
System.out.println("線程 " + Thread.currentThread().getName()
+ "生產(chǎn)" + i);
this.notify();
} catch (InterruptedException e) {
return;
}
}
}
}
public void addData() {
while (!Thread.interrupted()) {
synchronized (this) {
try {
while (i == 0) {
this.wait();
}
result += i;
System.out.println("線程 " + Thread.currentThread().getName()
+ "消費" + i + "--result=" + result);
i = 0;
this.notify();
} catch (InterruptedException e) {
return;
}
}
}
}
}
5.其他協(xié)調(diào)方法
1.join()
一個線程可以調(diào)用其他線程的join()方法,其效果是等待其他線程結(jié)束才繼續(xù)執(zhí)行橱鹏。如果某個線程調(diào)用t.join()膜蠢,此線程將被掛起堪藐,直到目標(biāo)線程t結(jié)束才恢復(fù)(即t.isAlive()為假)。
2.yield()
建議線程調(diào)度器讓其他具有相同優(yōu)先級的線程優(yōu)先運行挑围,但只是建議礁竞,并不一定就是別的線程先運行了
7.線程池
1.ExecutorService介紹
ExecutorService的生命周期包括三種狀態(tài):運行,關(guān)閉杉辙,終止模捂。創(chuàng)建后便進入了運行狀態(tài),當(dāng)調(diào)用了 shutdown()方法時蜘矢,便進入關(guān)閉狀態(tài)狂男。
使用線程池的好處:
- 降低資源消耗。通過重復(fù)利用已創(chuàng)的線程降低線程創(chuàng)建和銷毀在成的消耗
- 提高響應(yīng)速度品腹。當(dāng)任務(wù)到達的時候岖食,不需要再去創(chuàng)建線程就能立即執(zhí)行
- 提高線程的可管理性。線程是稀缺資源珍昨,如果無限制的創(chuàng)建不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性句喷,使用線程池可以進行統(tǒng)一的分配镣典、調(diào)優(yōu)和監(jiān)控
2.自定義線程池
public ThreadPoolExecutor (
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
corePoolSize:線程池中所保存的核心線程數(shù),包括空閑線程唾琼。
maximumPoolSize:池中允許的最大線程數(shù)兄春。
keepAliveTime:線程池中的空閑線程所能持續(xù)的最長時間,超過將被線程池移除,可以通過調(diào)大此值來提高線程的利用率锡溯。
unit:持續(xù)時間的單位赶舆。
-
workQueue:任務(wù)執(zhí)行前保存任務(wù)的隊列,僅保存由 execute 方法提交的 Runnable 任務(wù)祭饭。
ArrayBlockingQueue:是一個基于數(shù)組結(jié)構(gòu)的有界阻塞隊列芜茵,此隊列按 FIFO(先進先出)原則對元素進行排序。
LinkedBlockingQueue:一個基于鏈表結(jié)構(gòu)的阻塞隊列倡蝙,此隊列按FIFO (先進先出) 排序元素九串,吞吐量通常要高于ArrayBlockingQueue。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個隊列寺鸥。
SynchronousQueue:一個不存儲元素的阻塞隊列猪钮。每個插入操作必須等到另一個線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài)(緩沖區(qū)為1的生產(chǎn)者消費者模式)
PriorityBlockingQueue:一個具有優(yōu)先級的無限阻塞隊列胆建。
-
飽和策略
- AbortPolicy: 直接拋出異常烤低。
- CallerRunsPolicy:只用調(diào)用者所在線程來運行任務(wù)。
- DiscardOldestPolicy:丟棄隊列里最近的一個任務(wù)笆载,并執(zhí)行當(dāng)前任務(wù)扑馁。
- DiscardPolicy:不處理涯呻,丟棄掉。
- 當(dāng)然也可以根據(jù)應(yīng)用場景需要來實現(xiàn)RejectedExecutionHandler接口自定義策略檐蚜。如記錄日志或持久
3.創(chuàng)建線程池
以下四個線程池底層都是調(diào)用了ThreadPoolExecutor的構(gòu)造方法魄懂,所以它們主要只是參構(gòu)造數(shù)設(shè)置上的差異侨糟,理解了它們的默認構(gòu)造參數(shù)值就能明白它們的區(qū)別
newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 直接提交策略SynchronousQueue,無界線程池(maximumPoolSize無限大)鞭缭,可以進行自動線程回收
newFixedThreadPool(int)
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 使用了LinkedBlockingQueue無界隊列(workQueue無限大)辛润,線程數(shù)當(dāng)超過了coreSize攻泼,此隊列由于是鏈?zhǔn)絼t可以無限添加(資源耗盡炼绘,另當(dāng)別論)倒源,永遠也不會觸發(fā)產(chǎn)生新的線程,且線程結(jié)束即死亡不會被重復(fù)利用执泰。
newScheduledThreadPool(int)
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
延遲調(diào)用周期執(zhí)行示例,表示延遲1秒后每3秒執(zhí)行一次:
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
- 定長調(diào)度型線程池唧躲,這個池子里的線程可以按 schedule 依次 delay 執(zhí)行咙好,或周期執(zhí)行,這是特殊的DelayedWorkQueue的效果.
SingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(1, 1,0L,
TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
- 單例線程篡腌,任意時間池中只能有一個線程,如果向該線程池提交了多個任務(wù),這些任務(wù)將排隊
當(dāng)試圖通過 excute 方法將一個 Runnable 任務(wù)添加到線程池中時勾效,按照如下順序來處理:
ex=>start: 提交任務(wù)
co=>condition: 核心線程池滿了嘹悼?
new=>operation: 創(chuàng)建新線程
queue=>condition: 緩沖隊列無法加入?
add=>operation: 加入緩沖隊列
bao=>operation: 飽和政策處理
max=>condition: 最大線程池滿了?
e=>end
ex->co
co(yes)->queue
co(no)->new
queue(yes)->max
queue(no)->add
max(yes)->bao
max(no)->new
4.幾種排隊的策略
直接提交层宫。緩沖隊列采用 SynchronousQueue杨伙,它將任務(wù)直接交給線程處理而不保持它們。如果不存在可用于立即運行任務(wù)的線程(即無空閑線程)萌腿,則試圖把任務(wù)加入緩沖隊列將會失敗限匣,因此會構(gòu)造一個新的線程來處理新添加的任務(wù),并將其加入到線程池中毁菱。直接提交通常要求無界maximumPoolSizes(Integer.MAX_VALUE) 以避免拒絕新提交的任務(wù)米死。newCachedThreadPool 采用的便是這種策略。
無界隊列贮庞,典型的便是的LinkedBlockingQueue(鏈?zhǔn)?峦筒,當(dāng)線程數(shù)超過corePoolSize時,新的任務(wù)將在緩沖隊列無限排隊窗慎。因此勘天,創(chuàng)建的線程就不會超過corePoolSize,maximumPoolSize的值也就無效了捉邢。當(dāng)每個任務(wù)完全獨立于其他任務(wù)脯丝,即任務(wù)執(zhí)行互不影響時,適合于使用無界隊列伏伐。newFixedThreadPool采用的便是這種策略宠进。
有界隊列。當(dāng)使用有限的 maximumPoolSizes 時藐翎,有界隊列(一般緩沖隊列使用ArrayBlockingQueue材蹬,并制定隊列的最大長度)有助于防止資源耗盡实幕,但是可能較難調(diào)整和控制,隊列大小和最大池大小需要相互折衷堤器,需要設(shè)定合理的參數(shù)昆庇。
5.關(guān)閉線程池
- shutdown:不可以再submit新的task,已經(jīng)submit(分兩種闸溃,正在運行的和在緩沖隊列的)的將繼續(xù)執(zhí)行整吆。并interrupt()空閑線程。
- shutdownNow:試圖停止當(dāng)前正執(zhí)行的task辉川,清除未執(zhí)行的任務(wù)并返回尚未執(zhí)行的task的list表蝙。
- 只要調(diào)用了這兩個關(guān)閉方法的其中一個,isShutdown方法就會返回true乓旗。當(dāng)所有的任務(wù)都已關(guān)閉后,才表示線程池關(guān)閉成功府蛇,這時調(diào)用isTerminaed方法會返回true
6.Executor執(zhí)行Runnable和Callable任務(wù)
Runnable
無返回值,無法拋出經(jīng)過檢查的異常屿愚,通過execute方法添加
ExecutorService executorService = Executors.newSingleThreadExecutor();//創(chuàng)建線程池
executorService.execute(new TestRunnable());//添加任務(wù)
Callable
返回Future對象汇跨,獲取返回結(jié)果時可能會拋出異常,通過submit方法添加
package threadLearn;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class CallableDemo{
public static void main(String[] args){
ExecutorService executorService=Executors.newCachedThreadPool();
List<Future<String>> resultList = new ArrayList<Future<String>>();
//創(chuàng)建10個任務(wù)并執(zhí)行
for (int i = 0; i < 10; i++){
//使用ExecutorService執(zhí)行Callable類型的任務(wù)妆距,并將結(jié)果保存在future變量中
Future<String> future = executorService.submit(new TaskWithResult(i));
//將任務(wù)執(zhí)行結(jié)果存儲到List中
resultList.add(future);
}
//遍歷任務(wù)的結(jié)果
for (Future<String> fs : resultList){
try{
while(!fs.isDone()){//Future返回如果沒有完成穷遂,則一直循環(huán)等待,直到Future返回完成
System.out.println("還沒完成");
}
System.out.println(fs.get());//打印各個線程(任務(wù))執(zhí)行的結(jié)果
}catch(InterruptedException e){
e.printStackTrace();
}catch(ExecutionException e){
e.printStackTrace();
}finally{
//啟動一次順序關(guān)閉毅厚,執(zhí)行以前提交的任務(wù)塞颁,但不接受新任務(wù)
executorService.shutdown();
}
}
}
}
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id = id;
}
/**
* 任務(wù)的具體過程浦箱,一旦任務(wù)傳給ExecutorService的submit方法吸耿,
* 則該方法自動在一個線程上執(zhí)行
*/
public String call() throws Exception {
System.out.println("call()方法被自動調(diào)用" + Thread.currentThread().getName());
Thread.sleep(1000);
//該返回結(jié)果將被Future的get方法得到
return "call()方法被自動調(diào)用,任務(wù)返回的結(jié)果是:" + id + "-----" + Thread.currentThread().getName();
}
}
如果真正的結(jié)果的返回尚未完成酷窥,則get()方法會阻塞等待咽安,可以通過調(diào)用 isDone()方法判斷 Future 是否完成了返回。
9.線程異常處理
由于線程的本質(zhì)特性(可以理解不同的線程是平行空間)蓬推,從某個線程中逃逸的異常是無法被別的線程捕獲的妆棒。一旦異常逃出任務(wù)的run()方法,就會向外傳向控制臺沸伏。Thread.UncaughtExceptionHandler是JavaSE5中的新接口糕珊,它允許在每個Thread對象上都附著一個異常處理器。Thread.UncaughtExceptionHandler.uncaughtException()會在線程因未捕獲的異常而臨近死亡時被調(diào)用毅糟。
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
8.Lock 鎖與Condition
1.Lock鎖的介紹
Lock接口有3個實現(xiàn)它的類:ReentrantLock红选、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入鎖姆另、讀鎖和寫鎖喇肋。
- lock 必須被顯式地創(chuàng)建坟乾、鎖定和釋放,為了使用更多的功能蝶防,一般用 ReentrantLock 為其實例化
- 為了保證鎖最終一定會被釋放(可能會有異常發(fā)生)甚侣,要把互斥區(qū)放在 try 語句塊內(nèi),并在finally語句塊中釋放鎖间学,尤其當(dāng)有return語句時殷费,return 語句必須放在try字句中,以確保unlock()不會過早發(fā)生菱鸥,從而將數(shù)據(jù)暴露給第二個任務(wù)宗兼。
(1)ReentrantLock與synchronized的比較
等待可中斷:當(dāng)持有鎖的線程長期不釋放鎖時,正在等待的線程可以選擇放棄等待氮采,改為處理其他事情殷绍,由synchronized產(chǎn)生的互斥鎖時,會一直阻塞鹊漠,是不能被中斷的
可實現(xiàn)公平鎖:多個線程在等待同一個鎖時主到,必須按照申請鎖的時間順序排隊等待,通過構(gòu)造方法 ReentrantLock(ture)來要求使用公平鎖
鎖可以綁定多個條件:ReentrantLock 對象可以同時綁定多個 Condition 對象(名曰:條件變量或條件隊列)躯概,我們還可以通過綁定 Condition 對象來判斷當(dāng)前線程通知的是哪些線程(即與Condition對象綁定在一起的其他線程
(2)ReetrantLock的忽略中斷鎖和響應(yīng)中斷鎖
忽略中斷鎖與 synchronized實現(xiàn)的互斥鎖一樣登钥,不能響應(yīng)中斷,而響應(yīng)中斷鎖可以響應(yīng)中斷娶靡。
ReentrantLock lock = new ReentrantLock();
lock.lockInterruptibly();//獲取響應(yīng)中斷鎖
(3)讀寫鎖
用讀鎖來鎖定讀操作牧牢,用寫鎖來鎖定寫操作,這樣寫操作和寫操作之間會互斥姿锭,讀操作和寫操作之間會互斥塔鳍,但讀操作和讀操作就不會互斥。
ReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.writeLock().lock() //獲取寫鎖
rwl.readLock().lock() //獲取讀鎖
2.生產(chǎn)者-消費者模型Lock與Condition實現(xiàn)
package job_3;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class LockDatebuf implements Data {
private Integer i = 0;
private int result;
private Random random;
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public LockDatebuf() {
random = new Random();
}
public void sendData() throws InterruptedException {
while (!Thread.interrupted()) {
lock.lockInterruptibly();
try {
while (i != 0) {
condition.await();
}
i = random.nextInt(100);
System.out.println("線程 " + Thread.currentThread().getName()
+ "生產(chǎn)" + i);
condition.signal();
} catch (InterruptedException e) {
}finally{
lock.unlock();
}
}
}
public void addData() throws InterruptedException {
while (!Thread.interrupted()) {
lock.lockInterruptibly();
try {
while (i == 0) {
condition.await();
}
result += i;
System.out.println("線程 " + Thread.currentThread().getName()
+ "消費" + i + "--result=" + result);
i = 0;
condition.signal();
} catch (InterruptedException e) {
}finally{
lock.unlock();
}
}
}
}
9.并發(fā)新特性
1.CountDownLatch
可以讓一組任務(wù)必須在另一組任務(wù)全部結(jié)束后才開始執(zhí)行呻此,向CountDownLatch對象設(shè)置一個初始計數(shù)值轮纫,任何在這個對象上調(diào)用await()方法的線程都將阻塞,直至計數(shù)值子減為0焚鲜。其他任務(wù)在結(jié)束其工作時掌唾,可以調(diào)用countDown()來減小這個計數(shù)值。CountDownLatch被設(shè)計為只觸發(fā)一次忿磅。
package threadLearn;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
// 只觸發(fā)一次糯彬,計數(shù)值不能被重置
int size = 5;
CountDownLatch latch = new CountDownLatch(size);
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Waiting(latch,"wait線程"));
for (int i = 0; i < size; i++) {
exec.execute(new OtherTask(latch));
}
TimeUnit.SECONDS.sleep(1);
exec.shutdown();
}
}
class Waiting implements Runnable {
private CountDownLatch latch;
private String name;
public Waiting(CountDownLatch latch,String name) {
this.latch = latch;
this.name = name;
}
public void run() {
try {
latch.await();
System.out.println(name+"最后執(zhí)行的任務(wù)...");
} catch (Exception e) {
return;
}
}
}
class OtherTask implements Runnable {
private CountDownLatch latch;
private Random rand = new Random();
public OtherTask(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
doWork();
latch.countDown();
} catch (Exception e) {
return;
}
}
private void doWork() throws InterruptedException {
TimeUnit.MICROSECONDS.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 執(zhí)行完畢");
}
}
2.障礙器 CyclicBarrier
它適用于這樣一種情況:你希望創(chuàng)建一組任務(wù),它們并發(fā)地執(zhí)行工作葱她,另外的一個任務(wù)在這一組任務(wù)并發(fā)執(zhí)行結(jié)束前一直阻塞等待撩扒,直到該組任務(wù)全部執(zhí)行結(jié)束,這個任務(wù)才得以執(zhí)行览效。這非常像CountDownLatch却舀,只是 CountDownLatch是只觸發(fā)一次的事件虫几,而CyclicBarrier可以多次重用
package threadLearn;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
//創(chuàng)建CyclicBarrier對象,
//并設(shè)置執(zhí)行完一組5個線程的并發(fā)任務(wù)后挽拔,再執(zhí)行MainTask任務(wù)
CyclicBarrier cb = new CyclicBarrier(5, new MainTask());
new SubTask("A", cb).start();
new SubTask("B", cb).start();
new SubTask("C", cb).start();
new SubTask("D", cb).start();
new SubTask("E", cb).start();
}
}
/**
* 最后執(zhí)行的任務(wù)
*/
class MainTask implements Runnable {
public void run() {
System.out.println("......終于要執(zhí)行最后的任務(wù)了... ...");
}
}
/**
* 一組并發(fā)任務(wù)
*/
class SubTask extends Thread {
private String name;
private CyclicBarrier cb;
SubTask(String name, CyclicBarrier cb) {
this.name = name;
this.cb = cb;
}
public void run() {
System.out.println("[并發(fā)任務(wù)" + name + "] 開始執(zhí)行");
for (int i = 0; i < 999999; i++) ; //模擬耗時的任務(wù)
System.out.println("[并發(fā)任務(wù)" + name + "] 開始執(zhí)行完畢辆脸,通知障礙器");
try {
//每執(zhí)行完一項任務(wù)就通知障礙器
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
3.信號量Semaphore
信號量 Semaphore 實際上是一個功能完畢的計數(shù)信號量,從概念上講螃诅,它維護了一個許可集合啡氢,對控制一定資源的消費與回收有著很重要的意義。Semaphore 可以控制某個資源被同時訪問的任務(wù)數(shù)术裸,它通過acquire()獲取一個許可倘是,release()釋放一個許可。如果被同時訪問的任務(wù)數(shù)已滿袭艺,則其他 acquire 的任務(wù)進入等待狀態(tài)搀崭,直到有一個任務(wù)被release掉,它才能得到許可猾编。Semaphore 僅僅是對資源的并發(fā)訪問的任務(wù)數(shù)進行監(jiān)控瘤睹,而不會保證線程安全,因此答倡,在訪問的時候轰传,要自己控制線程的安全訪問。
10.性能調(diào)優(yōu)
(1)比較各類互斥技術(shù)
Atomic類
不激烈情況下瘪撇,性能比synchronized略遜获茬,而激烈的時候,也能維持常態(tài)倔既。激烈的時候恕曲,Atomic的性能會優(yōu)于ReentrantLock一倍左右。缺點是只能同步一個值叉存,一段代碼中只能出現(xiàn)一個Atomic的變量码俩,多于一個同步無效度帮。因為他不能在多個Atomic之間同步歼捏。關(guān)鍵字synchronized
在資源競爭不是很激烈的情況下,Synchronized的性能要優(yōu)于ReetrantLock笨篷,原因在于瞳秽,編譯程序通常會盡可能的進行優(yōu)化synchronizeLock
在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍率翅,但是ReetrantLock的性能能維持常態(tài)练俐。ReentrantLock提供了多樣化的同步,比如有時間限制的同步冕臭,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等腺晾。ReentrantLock擁有Synchronized相同的并發(fā)性和內(nèi)存語義燕锥,此外還多了 鎖投票,定時鎖等候和中斷鎖等候悯蝉」樾危可以被中斷。
(2)免鎖容器
CopyOnWiteArrayList的寫入將導(dǎo)致創(chuàng)建整個底層數(shù)組的副本鼻由,而原數(shù)組將保留在原地暇榴,使得復(fù)制的數(shù)組在被修改時,讀取操作可以安全的執(zhí)行蕉世。當(dāng)修改完成時蔼紧,一個原子性的操作把新的數(shù)組換入,使得新的讀取操作可以看到這個新的修改狠轻。
- 好處是當(dāng)多個迭代器同時遍歷和修改這個列表時奸例,不會拋出ConcurrentModificationException。
- CopyOnWriteArraySet將使用CopyOnWriteArrayList來實現(xiàn)其免鎖行為向楼。
- ConcurrenHashMap和ConcurrentLinkedQueue使用了類似的技術(shù)哩至,允許并發(fā)的讀取和寫入,但是容器中只有部分內(nèi)容而不是整個容器可以被復(fù)制和修改蜜自。在修改完成之前菩貌,讀取者仍舊不能看到他們。
(3)ReadWriteLock
對向數(shù)據(jù)結(jié)構(gòu)相對不頻繁的寫入重荠,但是有多個任務(wù)要經(jīng)常讀取這個數(shù)據(jù)結(jié)構(gòu)的這類情況進行了優(yōu)化箭阶。ReadWriteLock使得你可以同時有多個讀者,只要他們都不試圖寫入即可戈鲁。如果寫鎖已經(jīng)被其他任務(wù)持有仇参,那么任何讀者都不能訪問,直至這個寫鎖被釋放為止婆殿。即適用于讀者多于寫者的情況诈乒。
對于ReadWriteLock的應(yīng)用主要是:緩存和提高對數(shù)據(jù)結(jié)構(gòu)的并發(fā)性。
鎖降級:重入還允許從寫入鎖降級為讀取鎖婆芦,其實現(xiàn)方式是:先獲取寫入鎖怕磨,然后獲取讀取鎖,最后釋放寫入鎖消约。但是肠鲫,從讀取鎖升級到寫入鎖是不可能的。
鎖獲取的中斷:讀取鎖和寫入鎖都支持鎖獲取期間的中斷或粮。
Condition 支持 :寫入鎖提供了一個Condition實現(xiàn)导饲,對于寫入鎖來說,該實現(xiàn)的行為與 ReentrantLock.newCondition() 提供的 Condition 實現(xiàn)對 ReentrantLock 所做的行為相同。當(dāng)然渣锦,此 Condition 只能用于寫入鎖硝岗。
讀取鎖不支持 Condition,readLock().newCondition() 會拋出 UnsupportedOperationException袋毙。重入:此鎖允許 reader 和 writer 按照 ReentrantLock 的樣式重新獲取讀取鎖或?qū)懭腈i辈讶。在寫入線程保持的所有寫入鎖都已經(jīng)釋放后,才允許重入 reader 使用它們娄猫。
下面的代碼展示了如何利用重入來執(zhí)行鎖降級:
class CachedData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
//在獲取寫鎖之前必須釋放讀鎖
rwl.readLock().unlock();
rwl.writeLock().lock();
// 重新檢查狀態(tài)贱除,因為可能其他線程已經(jīng)獲取到讀鎖了
if (!cacheValid) {
data = ...
cacheValid = true;
}
rwl.readLock().lock();
rwl.writeLock().unlock();
}
use(data);
rwl.readLock().unlock();
}
}