Android 多線程入門

目錄:

目錄.png

1. 線程

1.1 基礎(chǔ)概念

說起線程的概念的時(shí)候與之相對(duì)應(yīng)的還有另一個(gè)概念--進(jìn)程愕掏,那么兩者有什么區(qū)別呢度秘?

  • 進(jìn)程:進(jìn)程是操作系統(tǒng)的基礎(chǔ),是系統(tǒng)機(jī)型資源分配和調(diào)度的基本單位饵撑。每一個(gè)進(jìn)程都有其獨(dú)立的代碼和數(shù)據(jù)空間剑梳,一個(gè)進(jìn)程里面有一個(gè)或多個(gè)線程唆貌。在Android系統(tǒng)當(dāng)中,進(jìn)程就是應(yīng)用程序的本體垢乙。
  • 線程:線程也被可以叫做輕量級(jí)的進(jìn)程锨咙,線程擁有自己的計(jì)數(shù)器以及堆棧等屬性,且可以訪問共享的內(nèi)存變量追逮。如在一個(gè)應(yīng)用程序當(dāng)中酪刀,對(duì)于網(wǎng)絡(luò)加載,IO操作等任務(wù)都是在不同的線程當(dāng)中進(jìn)行羊壹。

除了線程的概念蓖宦,還有一些在多線程的處理當(dāng)中經(jīng)常出現(xiàn)的名詞:

  • 主線程:JVM調(diào)用程序main()所產(chǎn)生的線程齐婴。
  • 當(dāng)前線程:這個(gè)是容易混淆的概念油猫。一般指通過Thread.currentThread()來獲取的進(jìn)程。
  • 后臺(tái)線程:指為其他線程提供服務(wù)的線程柠偶,也稱為守護(hù)線程情妖。JVM的垃圾回收線程就是一個(gè)后臺(tái)線程。用戶線程和守護(hù)線程的區(qū)別在于诱担,是否等待主線程依賴于主線程結(jié)束而結(jié)束
  • 前臺(tái)線程:是指接受后臺(tái)線程服務(wù)的線程毡证,其實(shí)前臺(tái)后臺(tái)線程是聯(lián)系在一起,就像傀儡和幕后操縱者一樣的關(guān)系蔫仙×暇Γ傀儡是前臺(tái)線程、幕后操縱者是后臺(tái)線程摇邦。由前臺(tái)線程創(chuàng)建的線程默認(rèn)也是前臺(tái)線程恤煞。可以通過isDaemon()和setDaemon()方法來判斷和設(shè)置一個(gè)線程是否為后臺(tái)線程施籍。

1.2 線程狀態(tài)轉(zhuǎn)換

多線程狀態(tài)轉(zhuǎn)換.png

線程整個(gè)生命周期當(dāng)中大致可以分為5個(gè)狀態(tài)(有些版本是6個(gè)居扒,本質(zhì)都是一樣的):

  1. 新建狀態(tài):線程被創(chuàng)建,調(diào)用start方法之前的狀態(tài)丑慎。
  2. 就緒狀態(tài):線程對(duì)象調(diào)用了start方法后會(huì)進(jìn)入就緒狀態(tài)喜喂,等待獲取CPU的資源。
  3. 運(yùn)行狀態(tài):獲取到運(yùn)行所需的CPU資源竿裂,開始執(zhí)行代碼玉吁。
  4. 阻塞狀態(tài):在運(yùn)行時(shí)由于某種原因進(jìn)入失去了CPU所分配的資源,暫時(shí)停止運(yùn)行腻异,知道再次獲得資源诈茧。
    • 等待阻塞:線程執(zhí)行wait()方法后進(jìn)入等待狀態(tài),釋放鎖捂掰。
    • 同步阻塞:當(dāng)線程獲取同步鎖時(shí)發(fā)現(xiàn)鎖被占用敢会,則進(jìn)入同步阻塞狀態(tài)曾沈。
    • 運(yùn)行阻塞:線程調(diào)用sleep(),或者join等方法進(jìn)入阻塞狀態(tài)。
  5. 死亡狀態(tài):線程代碼執(zhí)行完畢或者拋出異常鸥昏,結(jié)束運(yùn)行塞俱。

1.3 線程構(gòu)建

線程構(gòu)建有三種方式。

繼承Therad類

第一種實(shí)現(xiàn)方式便是直接繼承Thread類吏垮,具體的實(shí)現(xiàn)步驟如下:

(1)定義Thread的子類障涯,重寫run方法,run方法的方法體就代表了線程要完成的任務(wù)膳汪。因此唯蝶,run()方法被稱為執(zhí)行體。

(2)創(chuàng)建Thread子類的實(shí)例遗嗽,即創(chuàng)建線程對(duì)象粘我。

(3)調(diào)用對(duì)象的start()方法啟動(dòng)線程。

代碼如下

//1. 繼承Thread類
public class TestThread extends Thread {
    private String mName;

    public TestThread(String name) {
        this.mName = name;
    }

    //2. 重寫run()方法痹换,在run()方法中執(zhí)行線程任務(wù)征字。
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            System.out.println("name is " + mName + i);
        }
    }

    public static void main(String[] args) {
        //3.創(chuàng)建Thread對(duì)象,調(diào)用start方法
        //這里需要注意娇豫,調(diào)用start方法不代表線程立即就會(huì)執(zhí)行匙姜,這時(shí)候只是進(jìn)入到了就緒狀態(tài),
        //當(dāng)獲得CPU資源之后會(huì)進(jìn)入到運(yùn)行狀態(tài)
        TestThread mThread = new TestThread("testThread");
        mThread.start();
    }
}

實(shí)現(xiàn)Runnable接口

第二種方式就是實(shí)現(xiàn)Runnable接口冯痢,具體操作步驟如下:

(1)自定義類并實(shí)現(xiàn)Runnable接口氮昧。
(2)創(chuàng)建Thread子類的實(shí)例,實(shí)現(xiàn)Runnable接口的對(duì)象作為參數(shù)實(shí)例化該Thread對(duì)象浦楣。
(3)調(diào)用Thread實(shí)例的start()方法袖肥。

// 1、 自定義類并實(shí)現(xiàn)Runnable接口椒振。
public class MutliThread implements Runnable{ 
    public void run(){
            System.out.println(ticket--+" is saled by "+name)昭伸;
    } 
}

public class TestRunnable{
public static void main(String[]  args){
// 2、 創(chuàng)建Thread子類的實(shí)例澎迎,
MutliThread  mTest  =  new  MutliThread();
Thread  mThread  =  new  Thread(mTest);
// 3庐杨、調(diào)用Thread實(shí)例的start()方法
mThread.start();
}
}

相比于直接繼承Thread類,更推薦實(shí)現(xiàn)Runnable接口的方式夹供,其優(yōu)點(diǎn)如下:

  1. 線程池只支持放入RunnableCallable的實(shí)現(xiàn)灵份。
  2. 多個(gè)Thread對(duì)象共用同一段代碼塊的情況下,實(shí)現(xiàn)Runnable接口更容易實(shí)現(xiàn)線程之間的數(shù)據(jù)共享。
  3. 避免單繼承的機(jī)制。

實(shí)現(xiàn)Callable接口

Callable接口屬于Executor框架中的功能類帖烘,Callable接口與Runnable接口的功能類似柳击,但提供了比Runnable接口更強(qiáng)大的功能:

  1. Callable可以再接收任務(wù)后提供一個(gè)返回值氛什,Runnable無法提供這個(gè)功能莺葫。
  2. Callable中call()方法可以拋出異常,而Runnable的run()方法不能跑出異常枪眉。
  3. Callable方法可以拿到一個(gè)Future對(duì)象捺檬,F(xiàn)uture對(duì)象表示一步計(jì)算的結(jié)果,它提供了價(jià)差計(jì)算是否完成的方法贸铜。

注意:使用Future的get()方法獲取結(jié)果的時(shí)候堡纬,當(dāng)前線程就會(huì)阻塞,直到call()方法返回結(jié)果蒿秦。

來看一下代碼實(shí)現(xiàn)

//1. 實(shí)現(xiàn)Callable<V>接口的call方法烤镐,V為返回值的類型
public class TestCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        Thread.sleep(5000);
        return "call finish";
    }

    public static void main(String[] args) {
        //2. 創(chuàng)建線程池(后面的內(nèi)容我們會(huì)詳細(xì)介紹)
        ExecutorService mService = Executors.newFixedThreadPool(1);
        //3. 創(chuàng)建TestCallable對(duì)象實(shí)例
        TestCallable callable = new TestCallable();
        //4. 通過Future獲取線程結(jié)構(gòu)
        Future<String> future = mService.submit(callable);
        System.out.println("程序開始運(yùn)行");
        try {
            System.out.println(future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println("程序終止");
        }
    }
}

如果有對(duì)線程的返回值加以控制的需求,推薦使用這種方式棍鳖,一般情況下我們采用實(shí)現(xiàn)Runnable接口的方式

1.4 線程常用方法

  • start():使線程開始執(zhí)行炮叶。
  • setName(String name):設(shè)置線程的名字為name值。
  • setPriority(int priority):設(shè)置線程優(yōu)先級(jí)鹊杖。
  • sleep(long millis):使線程睡眠悴灵,放棄CPU的使用權(quán)扛芽,但不會(huì)釋放骂蓖。
  • yield():使當(dāng)前線程回到就緒狀態(tài),將CPU使用權(quán)交給同級(jí)或者更高級(jí)線程使用川尖。

注意:yield()方法在交出CPU使用權(quán)以后又會(huì)和其他線程一同競(jìng)爭(zhēng)資源登下,可能會(huì)再次執(zhí)行當(dāng)前線程。sleep(millis)方法則是在指定時(shí)間內(nèi)不再競(jìng)爭(zhēng)資源叮喳。且yield()方法不會(huì)將資源出讓給更低優(yōu)先級(jí)的線程被芳,sleep(millis)方法可以。

  • join():等待線程終止馍悟。
  • currentThread():獲取當(dāng)前線程畔濒。
  • interrupt():使線程中的中斷標(biāo)志位置為中斷,如果需要中斷線程锣咒,需要在線程當(dāng)中對(duì)中斷進(jìn)行控制侵状。
  • wait():主動(dòng)釋放鎖對(duì)象,同時(shí)使線程休眠毅整,趣兄。
  • notify():喚起休眠線程,并重新獲取鎖對(duì)象悼嫉。

相比于sleep(long millis)方法艇潭,wait()方法會(huì)釋放鎖對(duì)象而sleep()方法不會(huì),而wait()方法必須出現(xiàn)在synchronized(Obj){...}語句塊內(nèi)并與notify()方法配合使用,

2. 同步

在線程的使用中蹋凝,如果存在兩個(gè)或者兩個(gè)以上的線程需要共享同一段數(shù)據(jù)鲁纠,如果多個(gè)線程同時(shí)對(duì)數(shù)據(jù)對(duì)象進(jìn)行操作,就可能會(huì)引發(fā)競(jìng)爭(zhēng)條件鳍寂,如果進(jìn)行同步處理的話就有可能會(huì)引發(fā)問題房交。

同步的實(shí)現(xiàn)有兩種:Lock以及synchronized

2.1 Lock

Lock位于java.util.concurrent.locks包下伐割,是Java提供的一個(gè)接口候味,提供了如下方法:

public interface Lock {
    //獲取鎖,如果鎖已經(jīng)被其他線程獲取則等待隔心。
    void lock();
    //如果線程正在等待獲取鎖白群,則這個(gè)線程能夠響應(yīng)中斷,當(dāng)兩個(gè)線程同時(shí)通過lock.lockInterruptibly()想獲取某個(gè)鎖時(shí)硬霍,
    //假若此時(shí)線程A獲取到了鎖帜慢,而線程B只有在等待,那么對(duì)線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過程唯卖。
    void lockInterruptibly() throws InterruptedException;
    //嘗試獲取鎖粱玲,如果獲取成功則返回true,如果失敗則返回false
    boolean tryLock();
    //與tryLoc()類似拜轨,只不過是在time響應(yīng)時(shí)間內(nèi)去嘗試獲取鎖抽减,如果獲取失敗返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    //釋放鎖,一定要在finally代碼塊當(dāng)中調(diào)用橄碾。
    void unlock();
    //獲取鎖對(duì)應(yīng)的條件對(duì)象
    Condition newCondition();
}

在項(xiàng)目當(dāng)中我們常用的是重如鎖ReentrantLock卵沉,ReentrantLock類對(duì)Lock接口做出了實(shí)現(xiàn)。
使用時(shí)其代碼結(jié)構(gòu)如下:

Lock mLock  =  new  ReentrantLock();
//
mLock.lock();
try{
//同步的代碼塊
......
}
finally{
//釋放鎖對(duì)象
mLock.unlock();
}

需要注意的是在finally當(dāng)中需要調(diào)用unlock()方法法牲,保證在執(zhí)行完代碼塊或者在發(fā)生異常的時(shí)候可以把鎖釋放掉史汗。

在實(shí)際使用時(shí),通常我們會(huì)在線程中設(shè)置條件用于執(zhí)行釋放鎖或者喚起其他線程拒垃,這時(shí)候就引入了條件對(duì)象Condition停撞。對(duì)上述代碼塊做如下修改:

Lock mLock  =  new  ReentrantLock();
Condition condition = mLock.newCondition();
mLock.lock();
try{
    while(臨界條件){
        condition.await();
    }
    //同步代碼操作  
    //signalAll()代表喚起所有等待線程,signal()表示喚起隨機(jī)線程
    condition.signalAll();
}
finally{
//釋放鎖對(duì)象
mLock.unlock();
}

Java還提供另一種鎖的形式:讀寫鎖ReadWriteLock悼瓮。它分離的讀和寫的操作戈毒,使用讀操作鎖時(shí)可以允許多個(gè)線程同時(shí)訪問,使用寫操作鎖時(shí)只允許一個(gè)線程進(jìn)行谤牡。提高了多個(gè)線程讀操作的效率副硅。

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

其使用方式如下:

ReadWriteLock mLock = new ReentrantReadWriteLock();

public void write(){
    lock.writeLock().lock();
    try{
        //寫操作
    }finally{
       lock.writeLock().unlock(); 
    }
}

public void read1(){
    lock.readLock().lock();
    try{
        //讀操作
    }finally{
       lock.readLock().unlock(); 
    }
}

public void read2(){
    lock.readLock().lock();
    try{
        //讀操作
    }finally{
       lock.readLock().unlock(); 
    }
}

2.2 synchronized

Lock的實(shí)現(xiàn)方式優(yōu)點(diǎn)在于它可以讓等待的線程去響應(yīng)中斷以及對(duì)讀寫操作的成本有所降低。但是很多情況下我們不需要這樣的控制翅萤,這個(gè)時(shí)候就可以直接使用synchronized關(guān)鍵字進(jìn)行控制恐疲。

可以用synchronized關(guān)鍵字修飾的內(nèi)容有代碼塊腊满、方法靜態(tài)方法培己。

修飾代碼塊

修飾一個(gè)代碼塊碳蛋,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號(hào){}括起來的代碼省咨,作用的對(duì)象是調(diào)用這個(gè)代碼塊的對(duì)象.

代碼如下

public class TestSynchronize implements Runnable {

    private static int count = 0;
    private String name;

    TestSynchronize(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("線程名為:" + Thread.currentThread().getName() 
                            + "----" + name + count++);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        TestSynchronize test1 = new TestSynchronize("Runnable A");
        Thread thread1 = new Thread(test1, "threadA");
        Thread thread2 = new Thread(test1, "threadB");
        thread1.start();
        thread2.start();
    }
}

運(yùn)行這段代碼輸出結(jié)果

線程名為:threadA----Runnable A0
線程名為:threadA----Runnable A1
線程名為:threadA----Runnable A2
線程名為:threadA----Runnable A3
線程名為:threadA----Runnable A4
線程名為:threadB----Runnable A5
線程名為:threadB----Runnable A6
線程名為:threadB----Runnable A7
線程名為:threadB----Runnable A8
線程名為:threadB----Runnable A9

從輸出結(jié)果可以看到肃弟,線程B會(huì)在線程A執(zhí)行結(jié)束后開始執(zhí)行,這是因?yàn)?code>線程A和線程B所使用的都是test1對(duì)象的鎖零蓉,在線程A執(zhí)行完畢之前笤受,它會(huì)一直占有這個(gè)鎖,直到執(zhí)行完畢敌蜂,線程B獲得鎖之后才開始執(zhí)行箩兽。

如果我們對(duì)上面代碼做如下修改

    TestSynchronize test1 = new TestSynchronize("Runnable A");
    TestSynchronize test2 = new TestSynchronize("Runnable B");
    Thread thread1 = new Thread(test1, "threadA");
    Thread thread2 = new Thread(test1, "threadB");

輸出結(jié)果為

線程名為:threadA----Runnable A0
線程名為:threadB----Runnable B1
線程名為:threadA----Runnable A2
線程名為:threadB----Runnable B3
線程名為:threadA----Runnable A4
線程名為:threadB----Runnable B5
線程名為:threadB----Runnable B6
線程名為:threadA----Runnable A6
線程名為:threadB----Runnable B7
線程名為:threadA----Runnable A8

這時(shí)候因?yàn)?code>線程A和線程B各自所持有的鎖時(shí)不同的,兩把鎖互不影響章喉,我們可以視為線程A線程B在同時(shí)運(yùn)行汗贫。

修飾方法

修飾方法即是在方法名前加上synchronized關(guān)鍵字,同步的作用范圍為整個(gè)方法秸脱,作用的對(duì)象為調(diào)用該方法所在的對(duì)象落包。將上面實(shí)例的同步方式修改為修飾方法的同步,其代碼結(jié)構(gòu)如下

    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("線程名為:" + Thread.currentThread().getName()
                        + "----" + name + count++);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

輸出結(jié)果與上述代碼相同摊唇。
修飾方法有幾點(diǎn)要注意:

  1. synchronized關(guān)鍵字不能繼承咐蝇。
  2. 在定義接口方法時(shí)不能使用synchronized關(guān)鍵字。
  3. 構(gòu)造方法不能使用synchronized關(guān)鍵字遏片,但可以使用synchronized代碼塊來進(jìn)行同步嘹害。

修飾靜態(tài)方法

修飾一個(gè)靜態(tài)的方法撮竿,其作用的范圍與修飾方法相同吮便,為整個(gè)方法代碼,但是其作用的對(duì)象是這個(gè)類的所有對(duì)象幢踏。在上面的代碼做如下修改

    @Override
    public void run() {
        method();
    }

    public synchronized static void method(){
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("線程名為:" + Thread.currentThread().getName()
                        + "----" + name + count++);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        TestSynchronize test1 = new TestSynchronize("Runnable A");
        TestSynchronize test2 = new TestSynchronize("Runnable B");
        Thread thread1 = new Thread(test1, "threadA");
        Thread thread2 = new Thread(test2, "threadB");
        thread1.start();
        thread2.start();
    }

輸出結(jié)果變?yōu)?/p>

線程名為:threadA----Runnable B0
線程名為:threadA----Runnable B1
線程名為:threadA----Runnable B2
線程名為:threadA----Runnable B3
線程名為:threadA----Runnable B4
線程名為:threadB----Runnable B5
線程名為:threadB----Runnable B6
線程名為:threadB----Runnable B7
線程名為:threadB----Runnable B8
線程名為:threadB----Runnable B9

因?yàn)檫@種方式下synchronized作用的對(duì)象為類的所有對(duì)象髓需,所以test1test2擁有的是同一個(gè)鎖,所以他們運(yùn)行時(shí)也是互斥的房蝉,只有在A運(yùn)行完畢之后B才會(huì)運(yùn)行僚匆。

修飾類

修改一個(gè)類,其作用的范圍是synchronized后面括號(hào)括起來的部分搭幻,作用的對(duì)象是這個(gè)類的所有對(duì)象咧擂。其使用方式如下

    public void run() {
        synchronized (TestSynchronize.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("線程名為:" + Thread.currentThread().getName()
                            + "----" + name + count++);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

輸出結(jié)果與修飾靜態(tài)方法時(shí)相同。

2.3 volatile

如果所需要同步的對(duì)象只是某一個(gè)或兩個(gè)實(shí)例對(duì)象時(shí)檀蹋,我們可以使用volatile關(guān)鍵字來代替松申。

線程讀寫內(nèi)存變量模型如下。


內(nèi)存模型.png

根據(jù)圖示,線程A修改變量時(shí)需要讀取本地內(nèi)存中的內(nèi)容贸桶,修改完后存儲(chǔ)到本地內(nèi)存在同步到主存當(dāng)中舅逸。線程B需要先把主存中的變量讀取到本地內(nèi)存中再進(jìn)行操作。當(dāng)線程A線程B同步進(jìn)行時(shí)皇筛,遍會(huì)發(fā)生問題琉历。

volatile關(guān)鍵字修飾的變量,保證了修改后的新值得可見性水醋,使得某一線程修改的內(nèi)容可以立即被其他線程獲取到旗笔。同時(shí)禁止編譯器和處理器對(duì)指令的重排序,從而確保多線程并發(fā)執(zhí)行的正確性拄踪。即volatile關(guān)鍵字可以保證可見性和有序性换团。

注意:volatile關(guān)鍵字無法保持原子性(原子性:即操作是不可中斷的,如賦值和讀取操作)宫蛆,所以在非原子性操作(如自增自減操作)的語句中不可以使用volatile關(guān)鍵字艘包。

3. 線程池

Java當(dāng)中,通過線程池對(duì)線程加以控制有三個(gè)優(yōu)點(diǎn):

  • 降低資源消耗:通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗耀盗。
  • 提高響應(yīng)速度:當(dāng)任務(wù)到達(dá)時(shí)想虎,任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
  • 提高線程的可管理性:線程是稀缺資源叛拷,如果無限制的創(chuàng)建舌厨,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性忿薇,使用線程池可以進(jìn)行統(tǒng)一的分配裙椭,調(diào)優(yōu)和監(jiān)控。

3.1 創(chuàng)建線程池

通過ThreadPoolExecutor類來構(gòu)建一個(gè)線程池署浩,ThreadPoolExecutor類有四個(gè)構(gòu)造方法揉燃,構(gòu)造參數(shù)最多的構(gòu)造方法如下:

- public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

各個(gè)參數(shù)對(duì)應(yīng)如下:

  • corePoolSize:核心線程數(shù)。如果當(dāng)前運(yùn)行線程數(shù)少于核心線程數(shù)筋栋,則創(chuàng)建新的線程來處理任務(wù)炊汤,如果多于corePoolSize,則加入到workQueue當(dāng)中弊攘。

關(guān)于核心線程數(shù)的配置抢腐?

任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理。CPU密集型任務(wù)配置盡可能少的線程數(shù)量襟交,如配置Ncpu+1個(gè)線程的線程池迈倍。IO密集型任務(wù)則由于需要等待IO操作,線程并不是一直在執(zhí)行任務(wù)捣域,則配置盡可能多的線程啼染,如2*Ncpu醋界。混合型的任務(wù)提完,如果可以拆分形纺,則將其拆分成一個(gè)CPU密集型任務(wù)和一個(gè)IO密集型任務(wù),只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大徒欣,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率逐样,如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大,則沒必要進(jìn)行分解打肝。

  • maximumPoolSize:線程池所允許的最大線程數(shù)脂新,如果當(dāng)任務(wù)隊(duì)列workQueue滿了,并且當(dāng)前線程數(shù)小于maximumPoolSize時(shí)粗梭,創(chuàng)建新的線程來處理任務(wù)争便,直到達(dá)到maximumPoolSize的數(shù)值。
  • keepAliveTime:設(shè)置非核心線程的閑置超時(shí)時(shí)間断医。如果非核心線程數(shù)閑置的時(shí)間超過keepAliveTime則回收線程滞乙。如果設(shè)置allowCoreThreadTimeOut為true時(shí),這個(gè)策略也會(huì)應(yīng)用到核心線程鉴嗤。
  • TimeUnit:keepAliveTime值得單位斩启。可選值包括NANOSECONDS(納米)醉锅、MICROSECONDS(微秒)兔簇、MILLISECONDS(毫秒)、SECONDS(秒)硬耍、MINUTES(分鐘)垄琐、HOURS(小時(shí))、DAYS(天)经柴。
  • workQueue:任務(wù)隊(duì)列狸窘,用來存儲(chǔ)未執(zhí)行的任務(wù)。

java當(dāng)中一個(gè)提供了七個(gè)阻塞隊(duì)列:

ArrayBlockQueue:由數(shù)組組成的有界阻塞隊(duì)列口锭。

LinkedBlockingQueue:由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列朦前。

PriorityBlockingQueue:支持優(yōu)先級(jí)排序的無界阻塞隊(duì)列。

DelayQueue:使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列鹃操。

SynchronousQueue:不存儲(chǔ)元素的阻塞隊(duì)列。

LinkedTransferQueue:由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列春哨。

LinkedBlockingDeque:由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列荆隘。

  • threadFactory:線程工廠,用來創(chuàng)建線程赴背,一般不需要設(shè)置椰拒。
  • RejectedExecutionHandler:表示當(dāng)拒絕處理任務(wù)時(shí)的策略

ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常晶渠。

ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常燃观。

ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù)褒脯,然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程)。

ThreadPoolExecutor.CallerRunsPolicy:由調(diào)用線程處理該任務(wù)缆毁。
提交任務(wù)后的線程池執(zhí)行流程如下:

線程池邏輯.png
  1. 提交任務(wù)后番川,先判斷是否達(dá)到核心線程數(shù),如果沒有則創(chuàng)建新的線程執(zhí)行任務(wù)脊框,如果達(dá)到了執(zhí)行下一步颁督。
  2. 判斷任務(wù)隊(duì)列是否已滿,如果沒有滿加入任務(wù)隊(duì)列浇雹,如果滿了就執(zhí)行下一步沉御。
  3. 判斷是否達(dá)到最大線程數(shù),如果沒有創(chuàng)建新的線程執(zhí)行任務(wù)昭灵,如果滿了就執(zhí)行飽和策略吠裆。

3.2 使用線程池

在線程池的使用中有幾個(gè)比較常用的方法。

  • submit():將線程放入線程池中烂完,并提供一個(gè)Future類型的返回值硫痰。
  • execute():將線程放入線程池中。
  • shutdown():調(diào)用該方法后窜护,線程池將不再接收新的線程效斑,當(dāng)線程池中所有線程執(zhí)行完畢后回收線程資源。
  • shutdownNow():馬上回收線程池柱徙。(慎用缓屠!可能會(huì)引起系統(tǒng)異常)

應(yīng)用代碼如下:

public class MyCallable implements Callable<String> {

    private String name;

    MyCallable(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        //新建一個(gè)核心線程數(shù)為3的線程池
        ExecutorService service = Executors.newFixedThreadPool(3);
        //results用來存儲(chǔ)線程結(jié)果
        List<Future<String>> results = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            results.add(service.submit(new MyCallable("Callable" + i)));
        }
        //關(guān)閉線程池,避免資源浪費(fèi)
        service.shutdown();
        //遍歷輸出結(jié)果
        for (Future<String> future : results) {
            try {
                System.out.println("result is " + future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public String call() throws Exception {
        System.out.println("TestThreadCall" + name);
        return name;
    }
}

輸出結(jié)果

TestThreadCallCallable0
TestThreadCallCallable1
TestThreadCallCallable2
TestThreadCallCallable3
TestThreadCallCallable4
TestThreadCallCallable5
TestThreadCallCallable6
TestThreadCallCallable7
TestThreadCallCallable9
TestThreadCallCallable8
result is Callable0
result is Callable1
result is Callable2
result is Callable3
result is Callable4
result is Callable5
result is Callable6
result is Callable7
result is Callable8
result is Callable9

4. Android的多線程

前面鋪墊了那么多护侮,終于回到了我們的老本行敌完,在Android里面依然可以使用Thread或者線程池的方式實(shí)現(xiàn)線程操作,但是如果涉及到對(duì)UI進(jìn)行操作羊初,還需要引入其他的線程管理方式滨溉。

4.1 Handler

定義:Handler主要用于接受子線程發(fā)送的數(shù)據(jù), 并用此數(shù)據(jù)配合主線程更新UI长赞。

背景:在我們的應(yīng)用啟動(dòng)時(shí)便會(huì)產(chǎn)生一個(gè)主線程(UI線程)晦攒,主要用于界面渲染,UI控制事件分發(fā)等功能得哆。但是在主線程當(dāng)中不可以做類似于網(wǎng)絡(luò)請(qǐng)求這種耗時(shí)操作脯颜,當(dāng)某個(gè)操作阻塞超過5秒時(shí)便會(huì)拋出異常,所以只能把這些操作放到子線程來操作贩据。但是因?yàn)锳ndroid主線程是線程不安全的栋操,所有的UI操作只能放到主線程來執(zhí)行闸餐,由此便產(chǎn)生了Handler

原理:Android主線程存在一個(gè)消息隊(duì)列矾芙,Handler通過sendMessage或者Post方法送消息舍沙,并把消息插入到消息隊(duì)列當(dāng)中。Looper循環(huán)檢測(cè)消息隊(duì)列剔宪,當(dāng)發(fā)現(xiàn)有未處理的消息時(shí)便回調(diào)到創(chuàng)建Handler的線程中重寫的handleMessage方法中去執(zhí)行拂铡。

Handler的使用

Post方式

  1. 新建Thread處理耗時(shí)操作。
  2. 創(chuàng)建一個(gè) handler歼跟,通過 handler.post/postDelay和媳,投遞創(chuàng)建的 Runnable,在 run 方法中進(jìn)行更新 UI 操作哈街。
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            //處理線程耗時(shí)操作

            handler.post(new Runnable() {
                @Override
                public void run() {
                    //主線程更新UI操作
                }
            });
        }
    });
    thread.start();

sendMessage方式

  1. 創(chuàng)建線程處理耗時(shí)操作留瞳。
  2. 創(chuàng)建Message對(duì)象并進(jìn)行設(shè)置。
  3. 調(diào)用sendMessage發(fā)送消息骚秦。
  4. 創(chuàng)建Hnadler并重寫handleMessage方法,對(duì)消息進(jìn)行處理她倘。
private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        //根據(jù)msg.what進(jìn)行消息劃分
        switch (msg.what) {      
            case 1:
                //進(jìn)行UI操作
                break;
        }
    }
};

public class TestThread extends Thread {

    @Override
    public void run() {
        super.run();
        //處理耗時(shí)操作
  
        //新建Message實(shí)例,
        Message msg = Message.obtain();  
        msg.obj = data;
        //設(shè)置 msg.what標(biāo)志位作箍,方便進(jìn)行消息類別劃分硬梁。
        msg.what=1;   
        //發(fā)送消息
        handler.sendMessage(msg);
    }
    
}
   
TestThread thread = new TestThread();
thread.start();

4.2 AsyncTask

定義:AsyncTask是android提供的輕量級(jí)的異步類,在類中實(shí)現(xiàn)異步操作,并提供接口反饋當(dāng)前異步執(zhí)行的程度(可以通過接口實(shí)現(xiàn)UI進(jìn)度更新)胞得,最后反饋執(zhí)行的結(jié)果給UI主線程荧止。

直接看示例分析

public class TestAsyncTask extends AsyncTask<String,Float,String> {
    /**
     * AsyncTask的三個(gè)參數(shù)分別為Params, Progress, Result
     * Params: 任務(wù)啟動(dòng)時(shí)輸入的參數(shù)類型
     * Progress: 任務(wù)進(jìn)度的返回類型
     * Result: 后臺(tái)任務(wù)的返回結(jié)果列席
     * 當(dāng)某個(gè)參數(shù)不需要傳遞參數(shù)時(shí),可以使用Void來代替
     */

    /**
     * onPreExecute()方法在后臺(tái)任務(wù)進(jìn)行之前執(zhí)行阶剑,一般用于處理部分初始化的操作
     * 在主線程進(jìn)行跃巡。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 該方法用于在后臺(tái)處理耗時(shí)操作,不可以進(jìn)行UI操作牧愁,可以通過publishProgress(Progress...)方法返回任務(wù)進(jìn)度
     * 在子線程進(jìn)行素邪。
     * @param strings 參數(shù)類型,對(duì)應(yīng)AsyncTask<Params, Progress, Result>的第一個(gè)參數(shù)
     * @return
     */
    @Override
    protected String doInBackground(String... strings) {
        return null;
    }

    /**
     * 該方法可以對(duì)UI進(jìn)行操作猪半,可以利用傳進(jìn)來的進(jìn)度對(duì)UI進(jìn)行更新
     * 在主線程進(jìn)行
     * @param values
     */
    @Override
    protected void onProgressUpdate(Float... values) {
        super.onProgressUpdate(values);
    }

    /**
     * 用來處理返回的結(jié)果兔朦,可以進(jìn)行UI操作
     * 在主線程進(jìn)行
     * @param s
     */
    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
    }
}

new TestAsyncTask().execute();

整個(gè)任務(wù)的執(zhí)行流程為:
onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()

注意:AsyncTask對(duì)象和execute()方法必須在UI線程調(diào)用,且一個(gè)任務(wù)實(shí)例只可以執(zhí)行一次。

總結(jié)

本篇內(nèi)容簡(jiǎn)單介紹了Android當(dāng)中用到的多線程的知識(shí)磨确,且大部分都屬于java當(dāng)中的內(nèi)容沽甥,熟練掌握多線程的內(nèi)容無論是在以后的工作中還是在面試中都是很有必要的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俐填,一起剝皮案震驚了整個(gè)濱河市安接,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌英融,老刑警劉巖盏檐,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異驶悟,居然都是意外死亡胡野,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門痕鳍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硫豆,“玉大人,你說我怎么就攤上這事笼呆⌒芟欤” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵诗赌,是天一觀的道長(zhǎng)汗茄。 經(jīng)常有香客問我,道長(zhǎng)铭若,這世上最難降的妖魔是什么洪碳? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮叼屠,結(jié)果婚禮上瞳腌,老公的妹妹穿的比我還像新娘。我一直安慰自己镜雨,他們只是感情好嫂侍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荚坞,像睡著了一般挑宠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上西剥,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天痹栖,我揣著相機(jī)與錄音,去河邊找鬼瞭空。 笑死揪阿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咆畏。 我是一名探鬼主播南捂,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼旧找!你這毒婦竟也來了溺健?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤钮蛛,失蹤者是張志新(化名)和其女友劉穎鞭缭,沒想到半個(gè)月后剖膳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岭辣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年吱晒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沦童。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仑濒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出偷遗,到底是詐尸還是另有隱情墩瞳,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布氏豌,位于F島的核電站喉酌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏箩溃。R本人自食惡果不足惜瞭吃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涣旨。 院中可真熱鬧歪架,春花似錦、人聲如沸霹陡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烹棉。三九已至攒霹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浆洗,已是汗流浹背催束。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伏社,地道東北人抠刺。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像摘昌,于是被迫代替她去往敵國(guó)和親速妖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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

  • ??一個(gè)任務(wù)通常就是一個(gè)程序聪黎,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程罕容。當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順...
    OmaiMoon閱讀 1,671評(píng)論 0 12
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,373評(píng)論 8 265
  • ——點(diǎn)點(diǎn)可可 我希望 每一個(gè)夜晚都有星光 每一個(gè)清晨都有朝陽 每一個(gè)孩子都快樂歡笑 每一個(gè)老人都幸福安康 我希望 ...
    點(diǎn)點(diǎn)可可閱讀 1,677評(píng)論 56 40
  • 最近有人向我反饋說锦秒,使用了ButterKnife8.8.1后出現(xiàn)空指針的問題露泊,但也有些人說沒問題。我看了這兩個(gè)人的...
    自由懶散的碼農(nóng)閱讀 5,042評(píng)論 7 8
  • 我們從未想過未來脂崔,卻總是迫不及待地去了解自己的未來滤淳。 很多時(shí)候梧喷,大多數(shù)人在人生中總是禁不住設(shè)想自己的以后:中考后砌左、...
    手掌心上的myoo閱讀 461評(píng)論 0 0