Java 并發(fā)入門

一、并發(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方法二意外死亡
image
image

要確定一個(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方法作為代替。
image
  • 拋異常:如果試圖的操作無(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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,011評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,323評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,874評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,095評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,605評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,551評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,720評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,961評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,330評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,690評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容