一、并發(fā)
進(jìn)程:每個(gè)進(jìn)程都擁有自己的一套變量
線程:線程之間共享數(shù)據(jù)
1.線程
Java中為多線程任務(wù)提供了很多的類。包括最基礎(chǔ)的Thread類、Runnable等接口,用于線程同步的鎖、阻塞隊(duì)列、同步器,使用線程池的執(zhí)行器、執(zhí)行框架,還有可以在多線程中使用的線程安全集合等。
(1)使用多線程給其他任務(wù)提供機(jī)會(huì)
創(chuàng)建線程:
- 將任務(wù)代碼移到實(shí)現(xiàn)了Runnable接口的類的run方法中,這個(gè)接口非常簡(jiǎn)單,只有一個(gè)方法:
public interface Runnable{
void run();
}
- 由Runnable創(chuàng)建一個(gè)Thread對(duì)象
Thread t = new Thread(r);
- 啟動(dòng)線程:
t.start();
注意:不要調(diào)用Thread或Runnable對(duì)象中的run方法,只會(huì)執(zhí)行同一個(gè)線程中的任務(wù),不會(huì)啟動(dòng)新的線程,應(yīng)該使用Thread的start方法。
二、中斷線程
沒(méi)有終止線程的方法,只能通過(guò)interrupt方法來(lái)請(qǐng)求中斷。
Thread或Runnable對(duì)象的run()方法包裝了新線程中執(zhí)行的代碼,在run()方法中遇到下面的情況,線程會(huì)終止。
- 正常終止。執(zhí)行完最后一條語(yǔ)句,也包括遇到return返回
- 異常終止。出現(xiàn)未捕獲的異常
強(qiáng)制結(jié)束:
- 調(diào)用Thread對(duì)象的stop()方法。拋出一個(gè)ThreadDeath異常,停止線程執(zhí)行(這個(gè)異常如果被捕獲一定要重新拋出)。這個(gè)方法已經(jīng)不推薦使用,原因是線程可能停止在一個(gè)不安全的狀態(tài)(例如轉(zhuǎn)賬操作,從一個(gè)賬號(hào)減了錢,還沒(méi)加到另一個(gè)賬號(hào),線程被強(qiáng)制結(jié)束了),應(yīng)該使用請(qǐng)求中斷的方式。
- 請(qǐng)求中斷方式。要結(jié)束一個(gè)線程,就設(shè)置該線程的中斷變量(調(diào)用Thread對(duì)象的interrupt()方法)(表明著有人想要中斷這個(gè)線程),線程中的代碼自己要負(fù)責(zé)查詢中斷變量(Thread類靜態(tài)方法interrupted()或Thread對(duì)象的isInterrupted()方法),如果發(fā)現(xiàn)中斷變量被設(shè)置了就自覺(jué)點(diǎn)不要再執(zhí)行了,恢復(fù)到安全的狀態(tài)后自行退出。請(qǐng)求中斷不是強(qiáng)制的,如果線程中的代碼不查詢中斷變量,或者發(fā)現(xiàn)中斷變量已經(jīng)被設(shè)置了但是不理會(huì)繼續(xù)厚著臉皮執(zhí)行,這個(gè)線程還是會(huì)一直運(yùn)行不會(huì)被停止。
//栗子
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread("MyThread");
t.start();
Thread.sleep(100);// 睡眠100毫秒
t.interrupt();// 中斷t線程
}
}
class MyThread extends Thread {
int i = 0;
public MyThread(String name) {
super(name);
}
public void run() {
while(!isInterrupted()) {// 當(dāng)前線程沒(méi)有被中斷,則執(zhí)行
System.out.println(getName() + getId() + "執(zhí)行了" + ++i + "次");
}
}
}
void interrupt()方法和InterruptedException特別說(shuō)明
- 如果調(diào)用interrupt方法時(shí),若線程正被某些可中斷的方法阻塞著(sleep,wait或可中斷IO調(diào)用等),那么現(xiàn)在肯定是無(wú)法檢測(cè)中斷狀態(tài)的,系統(tǒng)會(huì)清理中斷狀態(tài),拋出InterruptedException異常,阻塞的方法調(diào)用會(huì)立即被這個(gè)異常中斷。
- 如果調(diào)用interrupt方法將中斷狀態(tài)設(shè)置為了true,不久就調(diào)用了一個(gè)可中斷的方法(sleep,wait,可中斷IO調(diào)用等),這個(gè)調(diào)用不會(huì)成功,并且同樣會(huì)清除中斷狀態(tài)標(biāo)志,使中斷標(biāo)志為false,拋出InterruptedException異常。可見(jiàn),如果會(huì)循環(huán)調(diào)用sleep()這類可中斷的方法,就不需要再手動(dòng)檢測(cè)中斷狀態(tài)了。
- interrupt向線程發(fā)送中斷請(qǐng)求,線程的中斷狀態(tài)將被設(shè)置為true,如果目前線程被阻塞,那么InterruptedException異常將被拋出,中斷狀態(tài)會(huì)被設(shè)為false。
Thread的static boolean interrupted():
- 測(cè)試當(dāng)前線程是否被中斷,這一調(diào)用會(huì)產(chǎn)生一個(gè)副作用,它將當(dāng)前線程的中斷狀態(tài)重置為false
Thread的boolean isInterrupted():
- 測(cè)試線程是否被終止,這一調(diào)用不會(huì)改變線程的中斷狀態(tài)。
可見(jiàn)如果不設(shè)置中斷,InterruptedException肯定不會(huì)出現(xiàn),而只要拋出InterruptedException,設(shè)置的中斷狀態(tài)肯定已經(jīng)被清理了,這種情況只有InterruptedException這個(gè)異常是我們知道有中斷請(qǐng)求的唯一標(biāo)識(shí)了,因此我們要向外層通知有中斷發(fā)生,千萬(wàn)不要再把這個(gè)異常壓制住,否則怎么調(diào)用interrupt()方法請(qǐng)求中斷都不會(huì)有作用,線程中外層的代碼壓根不知道有中斷這回事,照常運(yùn)行。將這個(gè)中斷請(qǐng)求通知給外層有兩種方式:
- catch到InterruptedException時(shí),調(diào)用Thread.currentThread().interrupt(),重新把中斷狀態(tài)設(shè)置上,讓外層可以檢測(cè)到。
- 最好的方法是,不要再catch InterruptedException異常啦,只要有這個(gè)異常就往外層拋吧。一直拋到最外層,在Thread對(duì)象或Runnable對(duì)象的run()方法中處理這個(gè)異常(處理操作:恢復(fù)到安全的狀態(tài)然后自覺(jué)退出)。
public class Erupt {
static class MyInterruptableExceptionTask implements Runnable
{
private int begin=0;
public MyInterruptableExceptionTask(int s){begin=s;}
@Override
public void run() {
try {
int end=begin+10;
for(int i=begin; i<end; i++){
System.out.println("sub: "+i);
Thread.sleep(1000); //如果設(shè)置中斷時(shí)正在sleep,或設(shè)置完中斷后一個(gè)循環(huán)里遇到sleep,都會(huì)拋出InterruptedException異常,不需要再手動(dòng)檢測(cè)中斷狀態(tài)了
}
} catch (InterruptedException e) {
System.out.println("the call Thread.sleep(n) is interrupted by InterruptedExcetpion");
Thread.currentThread().interrupt(); //產(chǎn)生InterruptedException異常時(shí)中斷狀態(tài)被清除,所以要重新設(shè)置中斷或?qū)⒅袛喈惓O蛲鈷伋龉┖罄m(xù)代碼檢測(cè)是否發(fā)生了中斷
}
if(Thread.currentThread().isInterrupted())
System.out.println("sub thread is interrupted");
else
System.out.println("sub natural stop");
}
}
public static void main(String[] args) {
Thread t=new Thread(new MyInterruptableExceptionTask(111));
t.start();
for(int i=0; i<10; i++){
System.out.println("main: "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==5)
t.interrupt();
}
}
}
三、線程狀態(tài)
線程有6中狀態(tài):
- New:用new操作符創(chuàng)建一個(gè)新線程時(shí),如 new Thread(r), 該線程還沒(méi)有開始運(yùn)行。這意味著它的狀態(tài)是new,當(dāng)一個(gè)線程處在new狀態(tài),程序還沒(méi)有開始運(yùn)行線程中的代碼。在線程運(yùn)行之前還有一些基礎(chǔ)工作要做。
- Runnable:一旦調(diào)用start()方法,線程就處于runnable狀態(tài)。可以可運(yùn)行的線程可能正在運(yùn)行也可能沒(méi)有運(yùn)行,這取決于操作系統(tǒng)給線程提供運(yùn)行的時(shí)間(這就是為什么這個(gè)狀態(tài)成為可運(yùn)行而不是運(yùn)行),事實(shí)上,運(yùn)行中的線程被中斷,目的是為了讓他們線程獲得運(yùn)行機(jī)會(huì)。線程調(diào)度的細(xì)節(jié)依賴于操作系統(tǒng)提供的服務(wù)。搶占式調(diào)度系統(tǒng)給每一個(gè)可運(yùn)行線程一個(gè)時(shí)間片來(lái)執(zhí)行任務(wù),當(dāng)時(shí)間片用完,操作系統(tǒng)剝奪該線程的運(yùn)行權(quán),并給另一個(gè)線程可運(yùn)行機(jī)會(huì)。當(dāng)選擇下一個(gè)線程時(shí),操作系統(tǒng)考慮線程的優(yōu)先級(jí)。
- Bolocked:阻塞,當(dāng)一個(gè)線程試圖獲取一個(gè)內(nèi)部的對(duì)象鎖(而不是java.util.concurrent庫(kù)里的鎖), 而該鎖被其他線程持有,則該線程進(jìn)入阻塞狀態(tài)。當(dāng)其他線程釋放該鎖,并且線程調(diào)度器允許本線程持有它的時(shí)候,該線程將變成非阻塞狀態(tài)。
- Waiting:等待,當(dāng)線程通知另一個(gè)線程通知調(diào)度器一個(gè)條件時(shí),它自己進(jìn)入等待狀態(tài)。在調(diào)用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent庫(kù)中的Lock或Condition時(shí),就會(huì)出現(xiàn)這種情況。實(shí)際上,被阻塞狀態(tài)與被等待狀態(tài)是有很大不同的。
- Timed waiting:計(jì)時(shí)等待,有幾個(gè)方法有一個(gè)超時(shí)參數(shù)。調(diào)用它們導(dǎo)致線程進(jìn)入計(jì)時(shí)等待(timed waiting)狀態(tài)。這一狀態(tài)將一直保持到超時(shí)期滿或者接收到適當(dāng)?shù)耐ㄖв谐瑫r(shí)參數(shù)的方法有Thread.sleep和Object.wait, Thrad.join, Lock.tryLock以及Condition.await的計(jì)時(shí)版。
- Terminated:因?yàn)閞un方法正常退出而自然死亡;-因?yàn)橐粋€(gè)沒(méi)有捕獲的異常終止了run方法二意外死亡

要確定一個(gè)線程的當(dāng)前狀態(tài),可以調(diào)用Thread的getState()方法。
1.創(chuàng)建新線程
new Thread(r)
2.可運(yùn)行線程
調(diào)用start后,線程處于Runnable轉(zhuǎn)態(tài)
3.被阻塞線程和等待線程
- 當(dāng)試圖獲取一個(gè)內(nèi)部的對(duì)象鎖,而該鎖被他人持有,則進(jìn)入阻塞狀態(tài)。
- 當(dāng)線程等待另一個(gè)線程通知調(diào)度器一個(gè)條件時(shí),自己進(jìn)入等待狀態(tài)。
- 有幾個(gè)方法有一個(gè)超時(shí)參數(shù),調(diào)用他們導(dǎo)致線程進(jìn)入計(jì)時(shí)等待
4.被終止的線程
- 自然退出
- 沒(méi)有捕獲異常終止了run方法而以外死亡
- void join():阻塞調(diào)用此方法的線程(calling thread),直到線程t完成,此線程再繼續(xù);通常用于在main()主線程內(nèi),等待其它線程完成再結(jié)束main()主線程,底層通過(guò)wait實(shí)現(xiàn)的。
- void join(long millis):等待指定的線程死亡或者經(jīng)過(guò)指定的毫秒數(shù)
四、線程屬性
1.線程優(yōu)先級(jí)
默認(rèn)情況下,一個(gè)線程繼承它父親的優(yōu)先級(jí),可以用setPriority()方法來(lái)提高一個(gè)線程的優(yōu)先級(jí):
- MIN_PRIORITY:1
- MAX_PRIORITY:10
- NORM_PRIORITY:5
- void setPriority(int newPriority)
- static void yield():理論上,yield意味著放手,放棄,投降。一個(gè)調(diào)用yield()方法的線程告訴虛擬機(jī)它樂(lè)意讓其他線程占用自己的位置。這表明該線程沒(méi)有在做一些緊急的事情。注意,這僅是一個(gè)暗示,并不能保證不會(huì)產(chǎn)生任何影響。
2.守護(hù)線程
通過(guò)調(diào)用:
t.setDaemon(true)
可以將當(dāng)前線程轉(zhuǎn)換為守護(hù)線程。
守護(hù)線程的唯一用途是為其他線程提供服務(wù)。當(dāng)只剩下守護(hù)線程時(shí),虛擬機(jī)就退出來(lái)了。
守護(hù)線程很容易被中斷,所以盡量避免用守護(hù)線程去訪問(wèn)文件、數(shù)據(jù)庫(kù)等固有資源。
3.未捕獲異常處理器
線程運(yùn)行不能按照順序執(zhí)行過(guò)程中捕獲異常的方式來(lái)處理異常,異常會(huì)被直接拋出到控制臺(tái)(由于線程的本質(zhì),使得你不能捕獲從線程中逃逸的異常。一旦異常逃逸出任務(wù)的run方法,它就會(huì)向外傳播到控制臺(tái),除非你采用特殊的形式捕獲這種異常。),這樣會(huì)讓你很頭疼,無(wú)法捕捉到異常就無(wú)法處理異常而引發(fā)的問(wèn)題。
線程的run方法是不會(huì)拋出任何受查異常,所以異常需要被傳播到一個(gè)用于未捕獲異常的處理器,該處理器必須實(shí)現(xiàn)Thread.UncaughtExceptionHandler接口的類,這個(gè)接口只有一個(gè)方法:
void uncaughtException(Thread t,Throwable e);
/*
* 第一步:定義符合線程異常處理器規(guī)范的“異常處理器”
* 實(shí)現(xiàn)Thread.UncaughtExceptionHandler規(guī)范
*/
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
/*
* Thread.UncaughtExceptionHandler.uncaughtException()會(huì)在線程因未捕獲的異常而臨近死亡時(shí)被調(diào)用
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught "+e);
}
}
/*
* 第二步:定義線程工廠
* 線程工廠用來(lái)將任務(wù)附著給線程,并給該線程綁定一個(gè)異常處理器
*/
class HanlderThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
System.out.println(this+"creating new Thread");
Thread t = new Thread(r);
System.out.println("created "+t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());//設(shè)定線程工廠的異常處理器
System.out.println("eh="+t.getUncaughtExceptionHandler());
return t;
}
}
/*
* 第三步:我們的任務(wù)可能會(huì)拋出異常
* 顯示的拋出一個(gè)exception
*/
class ExceptionThread implements Runnable{
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("run() by "+t);
System.out.println("eh = "+t.getUncaughtExceptionHandler());
throw new RuntimeException();
}
}
/*
* 第四步:使用線程工廠創(chuàng)建線程池,并調(diào)用其execute方法
*/
public class ThreadExceptionUncaughtExceptionHandler{
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool(new HanlderThreadFactory());
exec.execute(new ExceptionThread());
}
}
可以使用setUncaughtExceptionHandler方法安裝一個(gè)處理器,也可以使用setDefaultUncaughtExceptionHandler為所有線程安裝一個(gè)默認(rèn)的處理器。
public static void main(String[] args){
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
ExecutorService exec =Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}
在java中要捕捉多線程產(chǎn)生的異常,需要自定義異常處理器,并設(shè)定到對(duì)應(yīng)的線程工廠中(即第一步和第二步)。
如果你知道將要在代碼中處使用相同的異常處理器,那么更簡(jiǎn)單的方式是在Thread類中設(shè)置一個(gè)靜態(tài)域,并將這個(gè)處理器設(shè)置為默認(rèn)的未捕獲處理器。
這個(gè)處理器只有在不存在線程專有的未捕獲異常處理器的情況下才會(huì)被調(diào)用。
五、同步
1.鎖對(duì)象
ReentrantLock是Java并發(fā)包中互斥鎖,它有公平鎖和非公平鎖兩種實(shí)現(xiàn)方式,以lock()為例,其使用方式為:
myLock.lock();
try{
critical section
}
finally{
myLock.unLock();
}
public void transfer(int from,int to,double amount){
bankLock.lock();
try{
if (accounts[from]<amount){
return;
}
System.out.println(Thread.currentThread());
accounts[from]-=amount;
System.out.printf(" %10.2f from %d to %d ",amount,from,to);
accounts[to]+=amount;
System.out.printf("Total Balance:%10.2f%n",getTotalBalance());
}
finally {
bankLock.unlock();
}
}
那么,ReentrantLock內(nèi)部是如何實(shí)現(xiàn)鎖的呢?接下來(lái)我們就以JDK1.7中的ReentrantLock的lock()為例詳細(xì)研究下。ReentrantLock類實(shí)現(xiàn)了Lock和java.io.Serializable接口,其內(nèi)部有一個(gè)實(shí)現(xiàn)鎖功能的關(guān)鍵成員變量Sync類型的sync,定義如下:
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
2.條件對(duì)象
使用一個(gè)條件對(duì)象來(lái)管理那些已經(jīng)獲得一個(gè)鎖但是不能做有用工作的線程
private Condition sufficentFunds;
private final double[] accounts ;
public Bank(int n,double initialBalance){
accounts = new double[n];
Arrays.fill(accounts,initialBalance);
sufficentFunds = bankLock.newCondition();
}
如果發(fā)現(xiàn)銀行存儲(chǔ)不足,等待直到另一個(gè)線程向賬戶注入資金,但是由于鎖對(duì)象的排他性,通過(guò)條件對(duì)象使當(dāng)前線程被阻塞,放棄了鎖。如果transfer發(fā)現(xiàn)資金不足,會(huì)調(diào)用sufficientFunds.await();而轉(zhuǎn)入線程調(diào)會(huì)調(diào)用sufficientFunds.singalAll()
sufficientFunds.singalAll():通知等待的線程可能條件滿足,可以讓sufficientFunds.await()返回阻塞地方再次測(cè)試一下。
singal是隨機(jī)選擇一條等待線程。
public class Bank {
private Lock bankLock = new ReentrantLock();
private Condition sufficentFunds;
private final double[] accounts ;
public Bank(int n,double initialBalance){
accounts = new double[n];
Arrays.fill(accounts,initialBalance);
sufficentFunds = bankLock.newCondition();
}
public void transfer(int from,int to,double amount){
bankLock.lock();
try{
if (accounts[from]<amount){
sufficentFunds.await();
}
System.out.println(Thread.currentThread());
accounts[from]-=amount;
System.out.printf(" %10.2f from %d to %d ",amount,from,to);
accounts[to]+=amount;
System.out.printf("Total Balance:%10.2f%n",getTotalBalance());
sufficentFunds.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bankLock.unlock();
}
}
private double getTotalBalance() {
double sum = 0;
for (double a:accounts){
sum+=a;
}
return sum;
}
public int size(){
return accounts.length;
}
}
3.synchronized關(guān)鍵字
鎖和條件對(duì)象的關(guān)鍵之處:
- 鎖用來(lái)保護(hù)代碼片段,任何時(shí)刻只能有一個(gè)線程執(zhí)行保護(hù)的代碼
- 鎖可以管理試圖進(jìn)入被保護(hù)代碼段的線程
- 鎖可以擁有一個(gè)或多個(gè)相關(guān)的條件對(duì)象
- 每個(gè)條件對(duì)象管理那些已經(jīng)進(jìn)入被保護(hù)代碼段但還不能運(yùn)行的線程
public synchronized void method(){
method body
}
等價(jià)于
public void method(){
this.intrinsiclock.lock();
try{
method
}
finally{
this.intrinsiclock.unlock();
}
}
wait方法等價(jià)于intrinsicCondition.awaiy();
notifyAll等價(jià)于intrinsicCondition.singalAll();
public class Bank {
private Lock bankLock = new ReentrantLock();
private Condition sufficentFunds;
private final double[] accounts ;
public Bank(int n,double initialBalance){
accounts = new double[n];
Arrays.fill(accounts,initialBalance);
sufficentFunds = bankLock.newCondition();
}
public synchronized void transfer(int from,int to,double amount) throws InterruptedException {
if (accounts[from]<amount){
wait();
}
System.out.println(Thread.currentThread());
accounts[from]-=amount;
System.out.printf(" %10.2f from %d to %d ",amount,from,to);
accounts[to]+=amount;
System.out.printf("Total Balance:%10.2f%n",getTotalBalance());
notifyAll();
}
private double getTotalBalance() {
double sum = 0;
for (double a:accounts){
sum+=a;
}
return sum;
}
public int size(){
return accounts.length;
}
}
(1)修飾一個(gè)代碼塊
一個(gè)線程訪問(wèn)一個(gè)對(duì)象中的synchronized(this)同步代碼塊時(shí),其他試圖訪問(wèn)該對(duì)象的線程將被阻塞。我們看下面一個(gè)例子:
/**
* 同步線程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
當(dāng)兩個(gè)并發(fā)線程(thread1和thread2)訪問(wèn)同一個(gè)對(duì)象(syncThread)中的synchronized代碼塊時(shí),在同一時(shí)刻只能有一個(gè)線程得到執(zhí)行,另一個(gè)線程受阻塞,必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。Thread1和thread2是互斥的,因?yàn)樵趫?zhí)行synchronized代碼塊時(shí)會(huì)鎖定當(dāng)前的對(duì)象,只有執(zhí)行完該代碼塊才能釋放該對(duì)象鎖,下一個(gè)線程才能執(zhí)行并鎖定該對(duì)象。
我們?cè)侔裇yncThread的調(diào)用稍微改一下:
Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();
SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9
這時(shí)創(chuàng)建了兩個(gè)SyncThread的對(duì)象syncThread1和syncThread2,線程thread1執(zhí)行的是syncThread1對(duì)象中的synchronized代碼(run),而線程thread2執(zhí)行的是syncThread2對(duì)象中的synchronized代碼(run);我們知道synchronized鎖定的是對(duì)象,這時(shí)會(huì)有兩把鎖分別鎖定syncThread1對(duì)象和syncThread2對(duì)象,而這兩把鎖是互不干擾的,不形成互斥,所以兩個(gè)線程可以同時(shí)執(zhí)行。
(2)當(dāng)一個(gè)線程訪問(wèn)對(duì)象的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問(wèn)該對(duì)象中的非synchronized(this)同步代碼塊。
public class SyncTest implements Runnable{
private static int count;
@Override
public void run() {
for (int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void print(){
for (int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+(count++));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SyncTest s = new SyncTest();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
}
}
Thread-1:0
Thread-0:0
Thread-1:1
Thread-0:2
Thread-1:3
Thread-0:4
Thread-0:5
Thread-1:5
Thread-0:7
Thread-1:6
(3)指定要給某個(gè)對(duì)象加鎖
/**
* 銀行賬戶類
*/
class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
//存錢
public void deposit(float amt) {
amount += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取錢
public void withdraw(float amt) {
amount -= amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public float getBalance() {
return amount;
}
}
/**
* 賬戶操作類
*/
class AccountOperator implements Runnable{
private Account account;
public AccountOperator(Account account) {
this.account = account;
}
public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
}
}
}
Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}
Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0
在AccountOperator 類中的run方法里,我們用synchronized 給account對(duì)象加了鎖。這時(shí),當(dāng)一個(gè)線程訪問(wèn)account對(duì)象時(shí),其他試圖訪問(wèn)account對(duì)象的線程將會(huì)阻塞,直到該線程訪問(wèn)account對(duì)象結(jié)束。也就是說(shuō)誰(shuí)拿到那個(gè)鎖誰(shuí)就可以運(yùn)行它所控制的那段代碼。
當(dāng)有一個(gè)明確的對(duì)象作為鎖時(shí),就可以用類似下面這樣的方式寫程序。
public void method3(SomeObject obj)
{
//obj 鎖定的對(duì)象
synchronized(obj)
{
// todo
}
}
當(dāng)沒(méi)有明確的對(duì)象作為鎖,只是想讓一段代碼同步時(shí),可以創(chuàng)建一個(gè)特殊的對(duì)象來(lái)充當(dāng)鎖:
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance變量
public void method()
{
synchronized(lock) {
// todo 同步代碼塊
}
}
public void run() {
}
}
說(shuō)明:零長(zhǎng)度的byte數(shù)組對(duì)象創(chuàng)建起來(lái)將比任何對(duì)象都經(jīng)濟(jì)――查看編譯后的字節(jié)碼:生成零長(zhǎng)度的byte[]對(duì)象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。
(4)修飾一個(gè)方法
ynchronized修飾一個(gè)方法很簡(jiǎn)單,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修飾方法和修飾一個(gè)代碼塊類似,只是作用范圍不一樣,修飾代碼塊是大括號(hào)括起來(lái)的范圍,而修飾方法范圍是整個(gè)函數(shù)。
public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(5)Synchronized也可修飾一個(gè)靜態(tài)方法,用法如下:
public synchronized static void method() {
// todo
}
將靜態(tài)方法聲明為synchronize,如果調(diào)用這個(gè)方法,則該類的class對(duì)象的鎖被鎖住,其他線程可以調(diào)用同一個(gè)類的這個(gè)或任何其他同步靜態(tài)方法都需要等待當(dāng)前線程釋放。
(6)修飾一個(gè)類:
同修飾static的效果一樣
內(nèi)部鎖和條件存在的局限性:
- 不能中斷一個(gè)正在試圖獲取鎖的線程
- 試圖獲得鎖時(shí)不能設(shè)定超時(shí)
- 每個(gè)鎖僅有單一的條件,可能是不夠的
- 最好是不使用Lock/Condition也不使用synchronize關(guān)鍵字,多數(shù)情況下使用concurrent包中的一種機(jī)制
- synchronized盡量使用
- 有特殊情況才使用Lock/Conditions
7.Volatile域
有時(shí)候,僅僅為了同步一兩個(gè)實(shí)例域就使用synchronized關(guān)鍵字或是Lock/Condition,會(huì)造成很多不必要的開銷。這時(shí)候我們可以使用volatile關(guān)鍵字,使用volatile關(guān)鍵字修飾一個(gè)實(shí)例域會(huì)告訴編譯器和虛擬機(jī)這個(gè)域可能會(huì)被多線程并發(fā)訪問(wèn),這樣編譯器和虛擬機(jī)就能確保它的值總是我們所期望的。
volatile關(guān)鍵字的實(shí)現(xiàn)原理大致是這樣的:我們?cè)谠L問(wèn)內(nèi)存中的變量時(shí),通常都會(huì)把它緩存在寄存器中,以后再需要讀它的值時(shí),只需從相應(yīng)寄存器中讀取,若要對(duì)該變量進(jìn)行寫操作,則直接寫相應(yīng)寄存器,最后寫回該變量所在的內(nèi)存單元。若線程A把count變量的值緩存在寄存器中,并將count加2(將相應(yīng)寄存器的值加2),這時(shí)線程B被調(diào)度,它讀取count變量加2后并寫回。然后線程A又被調(diào)度,它會(huì)接著剛才的操作,也就是會(huì)把count值寫回,此時(shí)線程A是直接把寄存器中的值寫回count所在單元,而這個(gè)值是過(guò)期的。若count被volatile關(guān)鍵字修飾,這個(gè)問(wèn)題便可被圓滿解決。volatile變量有一個(gè)性質(zhì),就是任何時(shí)候讀取它的值時(shí),都會(huì)直接去相應(yīng)內(nèi)存單元讀取,而不是讀取緩存在寄存器中的值。這樣一來(lái),在上面那個(gè)場(chǎng)景中,線程A把count寫回時(shí),會(huì)從內(nèi)存中讀取count最新的值,從而確保了count的值總是我們所期望的。
8.final變量
關(guān)鍵字 final 可以視為 C++ 中 const 機(jī)制的一種受限版本,用于構(gòu)造不可變對(duì)象。final 類型的域是不能修改的(但如果 final 域所引用的對(duì)象時(shí)可變的,那么這些被引用的對(duì)象是可以修改的)。然而,在 Java 內(nèi)存模型中,final 域還有著特殊的語(yǔ)義。final 域能確保初始化過(guò)程的安全性,從而可以不受限制的訪問(wèn)不可變對(duì)象,并在共享這些對(duì)象時(shí)無(wú)需同步。
注: 個(gè)人理解為,final 字段一旦被初始化完成,并且構(gòu)造器沒(méi)有把 this 引用傳遞出去,那么在其他線程中就能看到 final 字段的值(域內(nèi)變量可見(jiàn)性,和 volatile 類似),而且其外部可見(jiàn)狀態(tài)永遠(yuǎn)也不會(huì)改變。它所帶來(lái)的安全性是最簡(jiǎn)單最純粹的。
注: 即使對(duì)象是可變的,通過(guò)將對(duì)象的某些域聲明為final類型,仍然可以 簡(jiǎn)化對(duì)狀態(tài)的判斷 ,因此限制對(duì)象的可變性也就相當(dāng)于限制了該對(duì)象可能的狀態(tài)集合。僅包含一個(gè)或兩個(gè)可變狀態(tài)的“基本不可變”對(duì)象仍然比包含多個(gè)可變狀態(tài)的對(duì)象簡(jiǎn)單。通過(guò)將域聲明為final類型,也相當(dāng)于告訴維護(hù)人員這些域是不會(huì)變化的。
正如“除非需要更高的可見(jiàn)性,否則應(yīng)將所有的餓域都聲明為私有域”[EJ Item 12]是一個(gè)良好的變成習(xí)慣,“除非需要某個(gè)域是可變的,否則應(yīng)將其聲明為final域”也是一個(gè)良好的變成習(xí)慣。
9.原子性
原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個(gè)操作是不可分割的,那么我們說(shuō)這個(gè)操作時(shí)原子操作。再比如:a++; 這個(gè)操作實(shí)際是a = a + 1;是可分割的,所以他不是一個(gè)原子操作。非原子操作都會(huì)存在線程安全問(wèn)題,需要我們使用同步技術(shù)(sychronized)來(lái)讓它變成一個(gè)原子操作。一個(gè)操作是原子操作,那么我們稱它具有原子性。Java的concurrent包下提供了一些原子類,我們可以通過(guò)閱讀API來(lái)了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
11.鎖測(cè)試與超時(shí)
通過(guò)tryLock()去試圖申請(qǐng)鎖,如果返回true則立即執(zhí)行,返回false則去做其他事
public class TestTryLock {
private List<Object> list = new ArrayList<Object>();
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
final TestTryLock test = new TestTryLock();
new Thread("第一個(gè)線程 ") {
@Override
public void run() {
test.doSomething(Thread.currentThread());
}
}.start();
new Thread("第二個(gè)線程 ") {
@Override
public void run() {
test.doSomething(Thread.currentThread());
}
}.start();
}
public void doSomething(Thread thread) {
if (lock.tryLock()) {
try {
System.out.println(thread.getName() + "得到了鎖.");
for (int i = 0; i < 10; i++) {
list.add(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(thread.getName() + "釋放了鎖.");
lock.unlock();
}
} else {
System.out.println(thread.getName() + "獲取鎖失敗.");
}
}
}
以上代碼運(yùn)行結(jié)果如下:
第一個(gè)線程 得到了鎖.
第一個(gè)線程 釋放了鎖.
第二個(gè)線程 得到了鎖.
第二個(gè)線程 釋放了鎖.
12.讀寫鎖
若很多線程從一個(gè)內(nèi)存區(qū)域讀取數(shù)據(jù),但其中只有極少的一部分線程會(huì)對(duì)其中的數(shù)據(jù)進(jìn)行修改,此時(shí)我們希望所有Reader線程共享數(shù)據(jù),而所有Writer線程對(duì)數(shù)據(jù)的訪問(wèn)要互斥。我們可以使用讀/寫鎖來(lái)達(dá)到這一目的。
Java中的讀/寫鎖對(duì)應(yīng)著ReentrantReadWriteLock類,它實(shí)現(xiàn)了ReadWriteLock接口,這個(gè)接口的定義如下:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
我們可以看到這個(gè)接口就定義了兩個(gè)方法,其中readLock方法用來(lái)獲取一個(gè)“讀鎖”,writeLock方法用來(lái)獲取一個(gè)“寫鎖”。
ReentrantReadWriteLock類的使用步驟通常如下所示:
//構(gòu)造一個(gè)ReentrantReadWriteLock對(duì)象
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//分別從中“提取”讀鎖和寫鎖
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
//對(duì)所有的Reader線程加讀鎖
readLock.lock();
try {
//讀操作可并發(fā),但寫操作會(huì)互斥
} finally {
readLock.unlock();
}
//對(duì)所有的Writer線程加寫鎖
writeLock.lock();
try {
//排斥所有其他線程的讀和寫操作
} finally {
writeLock.unlock();
}
在使用ReentrantReadWriteLock類時(shí),我們需要注意以下兩點(diǎn):
- 若當(dāng)前已經(jīng)有線程占用了讀鎖,其他要申請(qǐng)寫鎖的線程需要占用讀鎖的線程釋放了讀鎖才能申請(qǐng)成功;
- 若當(dāng)前已經(jīng)有線程占用了寫鎖,其他要申請(qǐng)讀鎖或?qū)戞i的線程都需要等待占用寫鎖的線程釋放了寫鎖才能申請(qǐng)成功。
13.為什么棄用stop和suspend方法
stop方法會(huì)終止所有未結(jié)束的方法,包括run方法,當(dāng)前線程被終止,立即釋放被它鎖住的所有對(duì)象的鎖,會(huì)導(dǎo)致對(duì)象狀態(tài)的不一致。
suspend是掛起一個(gè)持有鎖對(duì)象的線程,如果調(diào)用suspend方法的線程試圖獲取同一個(gè)鎖,就會(huì)產(chǎn)生死鎖。假如有A,B兩個(gè)線程,A線程在獲得某個(gè)鎖之后被suspend阻塞,這時(shí)A不能繼續(xù)執(zhí)行,線程B在或者相同的鎖之后才能調(diào)用resume方法將A喚醒,但是此時(shí)的鎖被A占有,B不能繼續(xù)執(zhí)行,也就不能及時(shí)的喚醒A,此時(shí)A,B兩個(gè)線程都不能繼續(xù)向下執(zhí)行而形成了死鎖。這就是suspend被棄用的原因。
六、阻塞隊(duì)列
以上我們所介紹的都屬于Java并發(fā)機(jī)制的底層基礎(chǔ)設(shè)施。在實(shí)際編程我們應(yīng)該盡量避免使用以上介紹的較為底層的機(jī)制,而使用Java類庫(kù)中提供給我們封裝好的較高層次的抽象。對(duì)于許多同步問(wèn)題,我們可以通過(guò)使用一個(gè)或多個(gè)隊(duì)列來(lái)解決:生產(chǎn)者線程向隊(duì)列中插入元素,消費(fèi)者線程則取出他們。考慮一下我們最開始提到的Counter類,我們可以通過(guò)隊(duì)列來(lái)這樣解決它的同步問(wèn)題:增加計(jì)數(shù)值的線程不能直接訪問(wèn)Counter對(duì)象,而是把a(bǔ)dd指令對(duì)象插入到隊(duì)列中,然后由另一個(gè)可訪問(wèn)Counter對(duì)象的線程從隊(duì)列中取出add指令對(duì)象并執(zhí)行add操作(只有這個(gè)線程能訪問(wèn)Counter對(duì)象,因此無(wú)需采取額外措施來(lái)同步)。
當(dāng)試圖向滿隊(duì)列中添加元素或者向空隊(duì)列中移除元素時(shí),阻塞隊(duì)列(blocking queue)會(huì)導(dǎo)致線程阻塞。通過(guò)阻塞隊(duì)列,我們可以按以下模式來(lái)工作:工作者線程可以周期性的將中間結(jié)果放入阻塞隊(duì)列中,其他線程可取出中間結(jié)果并進(jìn)行進(jìn)一步操作。若前者工作的比較慢(還沒(méi)來(lái)得及向隊(duì)列中插入元素),后者會(huì)等待它(試圖從空隊(duì)列中取元素從而阻塞);若前者運(yùn)行的快(試圖向滿隊(duì)列中插元素),它會(huì)等待其他線程。阻塞隊(duì)列提供了以下方法:
- add方法:添加一個(gè)元素。若隊(duì)列已滿,會(huì)拋出IllegalStateException異常。
- element方法:返回隊(duì)列的頭元素。若隊(duì)列為空,會(huì)拋出NoSuchElementException異常。
- offer方法:添加一個(gè)元素,若成功則返回true。若隊(duì)列已滿,則返回false。
- peek方法:返回隊(duì)列的頭元素。若隊(duì)列為空,則返回null。
- poll方法:刪除并返回隊(duì)列的頭元素。若隊(duì)列為空,則返回null。
- put方法:添加一個(gè)元素。若隊(duì)列已滿,則阻塞。
- remove方法:移除并返回頭元素。若隊(duì)列為空,會(huì)拋出NoSuchElementException。
- take方法:移除并返回頭元素。若隊(duì)列為空,則阻塞。
阻塞隊(duì)列的方法分為以下三種:
- 當(dāng)隊(duì)列作為線程管理工具,可以使用put和take作為阻塞線程的手段
- 當(dāng)向滿的隊(duì)列中添加或者從空的隊(duì)列中移出元素,add、remove和element操作將拋出異常。
- 當(dāng)隊(duì)列會(huì)在任何時(shí)刻滿或者空,要使用offer、poll、peek方法作為代替。
- 拋異常:如果試圖的操作無(wú)法立即執(zhí)行,拋一個(gè)異常。
- 特定值:如果試圖的操作無(wú)法立即執(zhí)行,返回一個(gè)特定的值(常常是 true / false)。
- 阻塞:如果試圖的操作無(wú)法立即執(zhí)行,該方法調(diào)用將會(huì)發(fā)生阻塞,直到能夠執(zhí)行。
- 超時(shí):如果試圖的操作無(wú)法立即執(zhí)行,該方法調(diào)用將會(huì)發(fā)生阻塞,直到能夠執(zhí)行,但等待時(shí)間不會(huì)超過(guò)給定值。返回一個(gè)特定值以告知該操作是否成功(典型的是true / false)。
BlockingQueue 是個(gè)接口,你需要使用它的實(shí)現(xiàn)之一來(lái)使用BlockingQueue,Java.util.concurrent包下具有以下 BlockingQueue 接口的實(shí)現(xiàn)類:
- ArrayBlockingQueue:ArrayBlockingQueue 是一個(gè)有界的阻塞隊(duì)列,其內(nèi)部實(shí)現(xiàn)是將對(duì)象放到一個(gè)數(shù)組里。有界也就意味著,它不能夠存儲(chǔ)無(wú)限多數(shù)量的元素。它有一個(gè)同一時(shí)間能夠存儲(chǔ)元素?cái)?shù)量的上限。你可以在對(duì)其初始化的時(shí)候設(shè)定這個(gè)上限,但之后就無(wú)法對(duì)這個(gè)上限進(jìn)行修改了(譯者注:因?yàn)樗腔跀?shù)組實(shí)現(xiàn)的,也就具有數(shù)組的特性:一旦初始化,大小就無(wú)法修改)。
- DelayQueue:DelayQueue 對(duì)元素進(jìn)行持有直到一個(gè)特定的延遲到期。注入其中的元素必須實(shí)現(xiàn) java.util.concurrent.Delayed 接口。
- LinkedBlockingQueue:LinkedBlockingQueue 內(nèi)部以一個(gè)鏈?zhǔn)浇Y(jié)構(gòu)(鏈接節(jié)點(diǎn))對(duì)其元素進(jìn)行存儲(chǔ)。如果需要的話,這一鏈?zhǔn)浇Y(jié)構(gòu)可以選擇一個(gè)上限。如果沒(méi)有定義上限,將使用 Integer.MAX_VALUE 作為上限。
- PriorityBlockingQueue:PriorityBlockingQueue 是一個(gè)無(wú)界的并發(fā)隊(duì)列。它使用了和類 java.util.PriorityQueue 一樣的排序規(guī)則。你無(wú)法向這個(gè)隊(duì)列中插入 null 值。所有插入到 PriorityBlockingQueue 的元素必須實(shí)現(xiàn) java.lang.Comparable 接口。因此該隊(duì)列中元素的排序就取決于你自己的 Comparable 實(shí)現(xiàn)。
- SynchronousQueue:SynchronousQueue 是一個(gè)特殊的隊(duì)列,它的內(nèi)部同時(shí)只能夠容納單個(gè)元素。如果該隊(duì)列已有一元素的話,試圖向隊(duì)列中插入一個(gè)新元素的線程將會(huì)阻塞,直到另一個(gè)線程將該元素從隊(duì)列中抽走。同樣,如果該隊(duì)列為空,試圖向隊(duì)列中抽取一個(gè)元素的線程將會(huì)阻塞,直到另一個(gè)線程向隊(duì)列中插入了一條新的元素。據(jù)此,把這個(gè)類稱作一個(gè)隊(duì)列顯然是夸大其詞了。它更多像是一個(gè)匯合點(diǎn)。
demo:
package blocking_queue;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author Sean
* @version 1.0
* @date 創(chuàng)建時(shí)間:2017/7/15 14:29
* @parameter
* @return
*/
public class BlockingQueueTest {
public static class Producer implements Runnable{
private final BlockingQueue<Integer> blockingQueue;
private volatile boolean flag;
private Random random;
public Producer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
flag = false;
random = new Random();
}
@Override
public void run() {
while (!flag){
int info = random.nextInt(100);
try {
blockingQueue.put(info);
System.out.println(Thread.currentThread().getName()+" procuce "+info);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void shutDown(){
flag = true;
}
}
public static class Consumer implements Runnable{
private final BlockingQueue<Integer> blockingQueue;
private volatile boolean flag;
public Consumer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void run() {
while(!flag){
int info;
try {
info = blockingQueue.take();
System.out.println(Thread.currentThread().getName()+" consumer "+info);
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void shutDown(){
flag=true;
}
}
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>(10);
Producer producer=new Producer(blockingQueue);
Consumer consumer=new Consumer(blockingQueue);
//創(chuàng)建5個(gè)生產(chǎn)者,5個(gè)消費(fèi)者
for(int i=0;i<10;i++){
if(i<5){
new Thread(producer,"producer"+i).start();
}else{
new Thread(consumer,"consumer"+(i-5)).start();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
producer.shutDown();
consumer.shutDown();
}
}
java.util.concurrent包提供了以下幾種阻塞隊(duì)列:
- LinkedBlockingQueue是一個(gè)基于鏈表實(shí)現(xiàn)的阻塞隊(duì)列。默認(rèn)容量沒(méi)有上限,但也有可以指定最大容量的構(gòu)造方法。它有的“雙端隊(duì)列版本”為L(zhǎng)inkedBlockingDeque。
- ArrayBlockingQueue是一個(gè)基于數(shù)組實(shí)現(xiàn)的阻塞隊(duì)列,它在構(gòu)造時(shí)需要指定容量。它還有一個(gè)構(gòu)造方法可以指定一個(gè)公平性參數(shù),若這個(gè)參數(shù)為true,那么等待了最長(zhǎng)時(shí)間的線程會(huì)得到優(yōu)先處理(指定公平性參數(shù)會(huì)降低性能)。
- PriorityBlockingQueue是一個(gè)基于堆實(shí)現(xiàn)的帶優(yōu)先級(jí)的阻塞隊(duì)列。元素會(huì)按照它們的優(yōu)先級(jí)被移除隊(duì)列。
package blockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueTest {
private int size = 20;
private ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(size);
public static void main(String[] args) {
BlockingQueueTest test = new BlockingQueueTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while(true){
try {
//從阻塞隊(duì)列中取出一個(gè)元素
blockingQueue.take();
System.out.println("隊(duì)列剩余" + blockingQueue.size() + "個(gè)元素");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true) {
try {
//向阻塞隊(duì)列中插入一個(gè)元素
blockingQueue.put(1);
System.out.println("隊(duì)列剩余空間:" + (size - blockingQueue.size()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在以上代碼中,我們有一個(gè)生產(chǎn)者線程不斷地向一個(gè)阻塞隊(duì)列中插入元素,同時(shí)消費(fèi)者線程從這個(gè)隊(duì)列中取出元素。若生產(chǎn)者生產(chǎn)的比較快,消費(fèi)者取的比較慢導(dǎo)致隊(duì)列滿,此時(shí)生產(chǎn)者再嘗試插入時(shí)就會(huì)阻塞在put方法中,直到消費(fèi)者取出一個(gè)元素;反過(guò)來(lái),若消費(fèi)者消費(fèi)的比較快,生產(chǎn)者生產(chǎn)的比較慢導(dǎo)致隊(duì)列空,此時(shí)消費(fèi)者嘗試從中取出時(shí)就會(huì)阻塞在take方法中,直到生產(chǎn)者插入一個(gè)元素。
七、Callable與Future
Callable和Future,它倆很有意思的,一個(gè)產(chǎn)生結(jié)果,一個(gè)拿到結(jié)果。
我們之前提到了創(chuàng)建線程的兩種方式,它們有一個(gè)共同的缺點(diǎn),那就是異步方法run沒(méi)有返回值,也就是說(shuō)我們無(wú)法直接獲取它的執(zhí)行結(jié)果,只能通過(guò)共享變量或者線程間通信等方式來(lái)獲取。好消息是通過(guò)使用Callable和Future,我們可以方便的獲得線程的執(zhí)行結(jié)果。
Callable接口與Runnable接口類似,區(qū)別在于它定義的異步方法call有返回值。Callable接口的定義如下:
public interface Callable<V> {
V call() throws Exception;
}
類型參數(shù)V即為異步方法call的返回值類型。
來(lái)看個(gè)簡(jiǎn)單栗子:
public class CallableAndFuture {
public static void main(String[] args) {
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
};
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
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í),再通過(guò)Future得到,豈不美哉!
Future可以對(duì)具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成以及獲取結(jié)果。可以通過(guò)get方法獲取執(zhí)行結(jié)果,該方法會(huì)阻塞直到任務(wù)返回結(jié)果。Future接口的定義如下:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在Future接口中聲明了5個(gè)方法,每個(gè)方法的作用如下:
cancel方法用來(lái)取消任務(wù),如果取消任務(wù)成功則返回true,如果取消任務(wù)失敗則返回false。參數(shù)mayInterruptIfRunning表示是否允許取消正在執(zhí)行卻沒(méi)有執(zhí)行完畢的任務(wù),如果設(shè)置true,則表示可以取消正在執(zhí)行過(guò)程中的任務(wù)。如果任務(wù)已經(jīng)完成,則無(wú)論mayInterruptIfRunning為true還是false,此方法肯定返回false(即如果取消已經(jīng)完成的任務(wù)會(huì)返回false);如果任務(wù)正在執(zhí)行,若mayInterruptIfRunning設(shè)置為true,則返回true,若mayInterruptIfRunning設(shè)置為false,則返回false;如果任務(wù)還沒(méi)有執(zhí)行,則無(wú)論mayInterruptIfRunning為true還是false,肯定返回true。
isCancelled方法表示任務(wù)是否被取消成功,如果在任務(wù)正常完成前被取消成功,則返回 true。
isDone方法表示任務(wù)是否已經(jīng)完成,若任務(wù)完成,則返回true;
get()方法用來(lái)獲取執(zhí)行結(jié)果,這個(gè)方法會(huì)阻塞,一直等到任務(wù)執(zhí)行完才返回;
-
get(long timeout, TimeUnit unit)用來(lái)獲取執(zhí)行結(jié)果,如果在指定時(shí)間內(nèi),還沒(méi)獲取到結(jié)果,就直接返回null。
Future接口的實(shí)現(xiàn)類是FutureTask:
public class FutureTask<V> implements RunnableFuture<V>
FutureTask類實(shí)現(xiàn)了RunnableFuture接口,這個(gè)接口的定義如下:
public interface RunnableFuture<V> implements Runnable, Future<V> {
void run();
}
可以看到RunnableFuture接口擴(kuò)展了Runnable接口和Future接口。
FutureTask類有如下兩個(gè)構(gòu)造器:
public FutureTask(Callable<V> callable)
public FutureTask(Runnable runnable, V result)
FutureTask通常與線程池配合使用,通常會(huì)創(chuàng)建一個(gè)包裝了Callable對(duì)象的FutureTask實(shí)例,并用submit方法將它提交到一個(gè)線程池去執(zhí)行,我們可以通過(guò)FutureTask的get方法獲取返回結(jié)果。
public class CallableAndFuture {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<Integer> future = threadPool.submit(new Callable<Integer>() {
public Integer call() throws Exception {
return new Random().nextInt(100);
}
});
try {
Thread.sleep(5000);// 可能做一些事情
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
九、執(zhí)行器
創(chuàng)建一個(gè)新線程涉及和操作系統(tǒng)的交互,因此會(huì)產(chǎn)生一定的開銷。在有些應(yīng)用場(chǎng)景下,我們會(huì)在程序中創(chuàng)建大量生命周期很短的線程,這時(shí)我們應(yīng)該使用線程池(thread pool)。通常,一個(gè)線程池中包含一些準(zhǔn)備運(yùn)行的空閑線程,每次將Runnable對(duì)象交給線程池,就會(huì)有一個(gè)線程執(zhí)行run方法。當(dāng)run方法執(zhí)行完畢時(shí),線程不會(huì)進(jìn)入Terminated
狀態(tài),而是在線程池中準(zhǔn)備等下一個(gè)Runnable到來(lái)時(shí)提供服務(wù)。使用線程池統(tǒng)一管理線程可以減少并發(fā)線程的數(shù)目,線程數(shù)過(guò)多往往會(huì)在線程上下文切換上以及同步操作上浪費(fèi)過(guò)多時(shí)間。
執(zhí)行器類(java.util.concurrent.Executors)提供了許多靜態(tài)工廠方法來(lái)構(gòu)建線程池。
1.線程池
在Java中,線程池通常指一個(gè)ThreadPoolExecutor對(duì)象,ThreadPoolExecutor類繼承了AbstractExecutorService類,而AbstractExecutorService抽象類實(shí)現(xiàn)了ExecutorService接口,ExecutorService接口又?jǐn)U展了Executor接口。也就是說(shuō),Executor接口是Java中實(shí)現(xiàn)線程池的最基本接口。我們?cè)谑褂镁€程池時(shí)通常不直接調(diào)用ThreadPoolExecutor類的構(gòu)造方法,二回使用Executors類提供給我們的靜態(tài)工廠方法,這些靜態(tài)工廠方法內(nèi)部會(huì)調(diào)用ThreadPoolExecutor的構(gòu)造方法,并為我們準(zhǔn)備好相應(yīng)的構(gòu)造參數(shù)。
Executor是類中的以下三個(gè)方法會(huì)返回一個(gè)實(shí)現(xiàn)了ExecutorService接口的ThreadPoolExecutor類的對(duì)象:
newCachedThreadPool() //返回一個(gè)帶緩存的線程池,該池在必要的時(shí)候創(chuàng)建線程,在線程空閑60s后終止線程
newFixedThreadPool(int threads) //返回一個(gè)線程池,線程數(shù)目由threads參數(shù)指明
newSingleThreadExecutor() //返回只含一個(gè)線程的線程池,它在一個(gè)單一的線程中依次執(zhí)行各個(gè)任務(wù)
newScheduledThreadPool()//包含預(yù)定執(zhí)行而構(gòu)建的線程池
- 對(duì)于newCachedThreadPool方法返回的線程池:對(duì)每個(gè)任務(wù),若有空閑線程可用,則立即讓它執(zhí)行任務(wù);若沒(méi)有可用的空閑線程,它就會(huì)創(chuàng)建一個(gè)新線程并加入線程池中;
- newFixedThreadPool方法返回的線程池里的線程數(shù)目由創(chuàng)建時(shí)指定,并一直保持不變。若提交給它的任務(wù)多于線程池中的空閑線程數(shù)目,那么就會(huì)把任務(wù)放到隊(duì)列中,當(dāng)其他任務(wù)執(zhí)行完畢后再來(lái)執(zhí)行它們;
- newSingleThreadExecutor會(huì)返回一個(gè)大小為1的線程池,由一個(gè)線程執(zhí)行提交的任務(wù)。
以下方法可將一個(gè)Runnable對(duì)象或Callable對(duì)象提交給線程池:
Future<T> submit(Callable<T> task)
Future<T> submit(Runnable task, T result)
Future<?> submit(Runnable task)
調(diào)用submit方法會(huì)返回一個(gè)Future對(duì)象,可通過(guò)這個(gè)對(duì)象查詢?cè)撊蝿?wù)的狀態(tài)。我們可以在這個(gè)Future對(duì)象上調(diào)用isDone、cancle、isCanceled等方法(Future接口會(huì)在下面進(jìn)行介紹)。第一個(gè)submit方法提交一個(gè)Callable對(duì)象到線程池中;第二個(gè)方法提交一個(gè)Runnable對(duì)象,并且Future的get方法在完成的時(shí)候返回指定的result對(duì)象。
當(dāng)我們使用完線程池時(shí),就調(diào)用shutdown方法,該方法會(huì)啟動(dòng)該線程池的關(guān)閉例程。被關(guān)閉的線程池不能再接受新的任務(wù),當(dāng)關(guān)閉前已存在的任務(wù)執(zhí)行完畢后,線程池死亡。shutdownNow方法可以取消線程池中尚未開始的任務(wù)并嘗試中斷所有線程池中正在運(yùn)行的線程。
在使用線程池時(shí),我們通常應(yīng)該按照以下步驟來(lái)進(jìn)行:
- 調(diào)用Executors中相關(guān)方法構(gòu)建一個(gè)線程池;
- 調(diào)用submit方法提交一個(gè)Runnable對(duì)象或Callable對(duì)象到線程池中;
- 若想要取消一個(gè)任務(wù),需要保存submit返回的Future對(duì)象;
- 當(dāng)不再提交任何任務(wù)時(shí),調(diào)用shutdown方法。
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
2.預(yù)定執(zhí)行
ScheduledExecutorService接口含有為預(yù)定執(zhí)行(Scheduled Execution)或重復(fù)執(zhí)行的任務(wù)專門設(shè)計(jì)的方法。Executors類的newScheduledThreadPool和newSingleThreadScheduledExecutor方法會(huì)返回實(shí)現(xiàn)了ScheduledExecutorService接口的對(duì)象。可以使用以下方法來(lái)預(yù)定執(zhí)行的任務(wù):
ScheduledFuture<V> schedule(Callable<V> task, long time, TimeUnit unit)
ScheduledFuture<?> schedule(Runnable task, long time, TimeUnit unit)
//以上兩個(gè)方法預(yù)定在指定時(shí)間過(guò)后執(zhí)行任務(wù)
SchedukedFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) //在指定的延遲(initialDelay)過(guò)后,周期性地執(zhí)行給定任務(wù)
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit) //在指定延遲(initialDelay)過(guò)后周期性的執(zhí)行任務(wù),每?jī)蓚€(gè)任務(wù)間的間隔為delay指定的時(shí)間
package test;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
}
}
3.控制任務(wù)組
對(duì)ExecutorService對(duì)象調(diào)用invokeAny方法可以把一個(gè)Callable對(duì)象集合提交到相應(yīng)的線程池中執(zhí)行,并返回某個(gè)已經(jīng)完成的任務(wù)的結(jié)果,該方法的定義如下:
T invokeAny(Collection<Callable<T>> tasks)
T invokeAny(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)
該方法可以指定一個(gè)超時(shí)參數(shù)。這個(gè)方法的不足在于我們無(wú)法知道它返回的結(jié)果是哪個(gè)任務(wù)執(zhí)行的結(jié)果。如果集合中的任意Callable對(duì)象的執(zhí)行結(jié)果都能滿足我們的需求的話,使用invokeAny方法是很好的。
invokeAll方法也會(huì)提交Callable對(duì)象集合到相應(yīng)的線程池中,并返回一個(gè)Future對(duì)象列表,代表所有任務(wù)的解決方案。該方法的定義如下:
List<Future<T>> invokeAll(Collection<Callable<T>> tasks)
List<Future<T>> invokeAll(Collection<Callable<T>> tasks, long timeout, TimeUnit unit)