Android 入門(十)異步線程池

要求:熟練使用線程、線程池和同步線程java

線程 Thread

在 Java 中線程的實現就是 Thread 類,通常實現線程有兩種方式,一種是繼承 Thread,一種是實現 Runnable 接口。bash

// 繼承 Thread
private class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        Log.i(TAG, "MyThread: " + Thread.currentThread().getName());
    }
}
MyThread myThread = new MyThread();
myThread.start();
複製代碼
// 實現 Runnable 接口
private class MyRunnable implements Runnable {
    @Override
    public void run() {
        Log.i(TAG, "MyRunnable: " + Thread.currentThread().getName());
    }
}
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
複製代碼
// 實現 Runnable 接口,使用匿名內部類
new Thread(new Runnable() {
    @Override
    public void run() {
        Log.i(TAG, "MyRunnable: " + Thread.currentThread().getName());
    }
}).start();
複製代碼

AsyncTask 異步任務

AsyncTask 是一個輕量級的異步任務類,它能夠在線程池中執行後臺任務,而後把執行的進度和結果傳遞給主線程,而且在主線程中更新 UI。多線程

AsyncTask 是一個抽象類,public abstract class AsyncTask<Params, Progress, Result>併發

其中,參數1 Params:異步任務的入參;參數2 Progress:執行任務的進度;參數3 Result:後臺任務執行的結果。dom

還提供了四個核心方法:異步

  • 方法一:onPreExcute() 在主線程中執行,任務開啓前的準備工做;
  • 方法二:doInBackground(Params... params) 是一個抽象方法,開啓子線程執行後臺任務;
  • 方法三:onProgressUpdate(Progress values) 在主線程中執行,更新 UI 進度;
  • 方法四:onPosExecute(Result result) 在子線程中執行,異步任務執行完成以後再執行,它的參數是 onProgressUpdate 方法的返回值。

簡單使用:ide

private static class MyAsyncTask extends AsyncTask<String, Integer, String> {

        private String mName;

        public MyAsyncTask(String name) {
            mName = name;
        }

        @Override
        protected String doInBackground(String... strings) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
            Log.i(mName, dateFormat.format(new Date(System.currentTimeMillis())));
            return null;
        }
    }
複製代碼

IntentService

IntentService 是一個實現了 HandlerThread 的 Service 抽象類。而 HandlerThread 是一個容許 Handler 的特殊線程。函數

簡單使用:性能

private class MyIntentService extends IntentService {
    public MyIntentService(String name) {
        super(name);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.i(TAG, "onHandleIntent: ");
    }
}
複製代碼

線程池

線程池就是一次建立多個線程,而後重用這幾個線程。ui

線程池的優勢:

  • 重用線程池中的線程,避免頻繁地建立和銷燬帶來的性能消耗;
  • 有效控制線程的最大併發數量,防止線程過大搶佔資源形成系統堵塞;
  • 能夠對線程進行必定的管理。

ThreadPoolExecutor

ExecutorService 是最基礎的線程池接口,ThreadPoolExecutor 類是對線程池的具體實現。下面經過經常使用的構造函數來分析一下用法。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
複製代碼

先解釋參數:

  • corePoolSize 表示線程池中核心線程的數量,在默認狀況下,即便核心線程沒有任務在執行,它也是存在的。若是將 ThreadPoolExecutor 的 allowCoreThreadTimeOut 屬性設置爲 true,那麼閒置的核心線程就會有超時策略,這個時間由 keepAliveTime 決定,keepAliveTime 時間內若是沒有迴應則該線程會被終止。反之,線程不會超時;
  • maximumPoolSize 表示線程池的最大線程數,當任務數量超過最大線程數時,其餘的任務可能會被阻塞。最大線程數 = 核心線程 + 非核心線程。非核心線程只有當核心線程不夠用的且線程池有空餘時纔會被建立,執行完任務後會被銷燬;
  • keepAliveTime 非核心線程的超時時長,當執行時間超過這個時間時,非核心線程就會被回收。當 allowCoreThreadTimeOut 爲 true 時,此屬性也做用在覈心線程;
  • unit 枚舉時間單位,TimeUnit;
  • workQueue 線程池中的任務隊列,咱們提交給線程池的 runnable 會被存儲在這個對象上。

線程池的分配遵循以下規則:

  • 當核心線程數量沒有達到 maximumPoolSize,會啓動核心線程執行任務;
  • 當核心線程數量達到了 maximumPoolSize,那麼任務會被放到任務隊列中排隊等待;
  • 若是任務隊列已滿,可是線程池中線程數量沒有達到 maximumPoolSize,那麼啓動一個非核心線程來處理任務。
  • 若是在任務隊列已滿,而且線程數量達到了 maximumPoolSize,那麼線程池會拒絕執行該任務,還會調用RejectedtionHandler的rejectedExecution方法來通知調用者。
下面四種線程池都是直接或者間接使用 ThreadPoolExecutor 實現

FixedThreadPool

經過 Executors.newFixedThreadPool(int nThreads) 方法建立,nThreads 就是固定的核心線程數,而且全部線程都是核心線程。它們沒有超時機制,而且排隊任務無限制,由於是核心線程,全部響應快,也不用擔憂線程被回收。

// 擁有固定的線程數,每一個線程都是核心線程
private ExecutorService mFixedThreadPool = Executors.newFixedThreadPool(5);

// 使用方法
for (int i = 0; i < 3; i++) {
    mFixedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: FixedThreadPool");
        }
    });
}
複製代碼

CachedThreadPool

經過 Executors.newCachedThreadPool() 方法建立。它是一個擁有無限多線程的線程池,而且全部線程都不是核心線程。當有新的任務來的時候沒有空閒的線程,會直接建立新的線程執行任務。全部線程的超時時間都是 60s,因此當線程空閒時必定會被系統回收,因此理論上該線程池不會有佔用系統資源的無用線程。

// 擁有無限多數量的線程池,全部線程都不是核心線程
private ExecutorService mCachedThreadPool = Executors.newCachedThreadPool();

// 使用方法
for (int i = 0; i < 3; i++) {
    mCachedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: CachedThreadPool");
        }
    });
}
複製代碼

ScheduledThreadPool

經過 Executors.newScheduledThreadPool(int corePoolSize) 建立,該線程池擁有固定的核心線程和無限量的非核心線程。而且非核心線程的超時時間爲 0s,也就是說一旦空閒就會立馬被回收。這類線程適合執行定時任務和固定週期的重複任務。

// 擁有固定的核心線程,還有無限多的非核心線程
private ExecutorService mScheduledThreadPool = Executors.newScheduledThreadPool(5);

// 使用方法
for (int i = 0; i < 3; i++) {
    mScheduledThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: ScheduledThreadPool");
        }
    });
}
複製代碼

SingleThreadExecutor

經過 Executors.newSingleThreadExecutor() 建立,它只有一個核心線程,而且確保傳進來的任務會被順序執行。它的意義是,統一全部外界任務到同一線程中,讓調用者忽略線程同步問題。

// 只有一個核心線程
private ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor();

// 使用方法
for (int i = 0; i < 3; i++) {
    mSingleThreadExecutor.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: SingleThreadExecutor");
        }
    });
}
複製代碼

線程池的經常使用方法有,shutDown() 關閉線程池,須要執行完已經提交的任務;shutDownNow() 關閉線程池,而且嘗試結束已經提交的任務;allowCoreThreadTimeOut(boolen) 容許核心線程閒置超時回收;execute() 提交任務無返回值;submit() 提交任務有返回值。

線程同步

synchronized

synchronized 鎖住的是對象,而且須要鎖住全局惟一的對象。因此 synchronized(this){} 方法不能奏效。

通常的作法是:

// 先建立一個全局惟一而且不會改變的對象
private final Object mLock = new Object();

// 鎖住想要順序執行的代碼
for (int i = 0; i < 3; i++) {
    mFixedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            synchronized (mLock) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
                Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
                Log.i(TAG, "run: FixedThreadPool");
            }
        }
    });
}
複製代碼

或者使用這個類對應的 Class 對象,如:

public void method(){
    synchronized(MainActivity.class){
    // Do something
    }
}
複製代碼

還可使用 static synchronize 方法修飾須要鎖住的方法,static 方法中沒法直接使用 this,也就是說它鎖住的不是 this,而是類的 Class 對象,因此 static synchronize 也至關於全局鎖。

public static synchronized void method(){
    // Do something
}
複製代碼

Object 的 wait、notify、notifyAll 方法

wait 方法:線程自動釋放佔有的對象鎖,並等待 notify; notify 方法:喚醒一個正在 wait 當前對象鎖的線程,並讓他拿到對象鎖; notifyAll 方法:喚醒全部正在 wait 當前對象鎖的線程。

notify 和 notifyAll 的區別:notify 是喚醒一個正在 wait 當前對象鎖的線程,而 notifyAll 是喚醒全部。notify 是本地方法,具體喚醒的是哪個線程,由 java 虛擬機定,notifyAll 喚醒的線程只是跳出 wait 狀態,接下來它們還會競爭對象鎖。

java中的wait、notify、notifyAll

Java 的 wait、notify、notifyAll

這三個方法是 Object 自帶的,而且在調用這三個方法時須要得到這個對象的鎖,不然就會報錯:

java.lang.IllegalMonitorStateException:current thread not owner
複製代碼

這三個方法以下:

  • wait:線程自動釋放其佔有的對象鎖,並等待 notify。
  • notify:喚醒一個正在 wait 當前對象鎖的線程,並讓它拿到鎖。notify 是一個本地方法,具體喚醒哪個線程由虛擬機決定。
  • notifyAll:喚醒全部正在 wait 當前對象鎖的線程。notifyAll 執行後並非全部線程立馬能往下執行,它們只是跳出 wait 狀態,仍是會競爭對象鎖。

參考:java中的wait、notify、notifyAll

CountDownLatch 更能優雅的線程同步

CountDownLatch 是一個線程輔助類,這個類可讓線程等待其餘線程完成一組操做以後才能執行,不然就一直等待。

首先使用一個整型參數來初始化,表示等待其餘線程的數量,使用 await() 方法讓線程開始等待其餘線程執行完畢,每個線程執行完以後調用 countDown() 方法,這會讓 CountDownLatch 內部的計數器減 1,當計數器變爲 0 的時候,CountDownLatch 將喚醒全部調用 await() 方法並進入 WAITING 狀態的線程。

簡單使用:

private void testCountDownLatch() {
        // 建立一個視頻會,而且須要 10 我的參加
        VideoConference videoConference = new VideoConference(10);
        Thread threadConference = new Thread(videoConference);
        threadConference.start();   // 開啓會議窗口

        // 建立 10 個參會人
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Participant("P-" + i, videoConference));
        }

        // 鏈接視頻會議
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }

    private class VideoConference implements Runnable {
        private CountDownLatch mController;

        public VideoConference(int number) {
            mController = new CountDownLatch(number);
        }

        public void arrive(String name) {
            System.out.printf("%s has arrived.\n", name);
            mController.countDown();
            System.out.printf("VideoConference: Waiting for %d participants.\n", mController.getCount());
        }

        @Override
        public void run() {
            System.out.printf("VideoConference: Initialization: %d participants.\n", mController.getCount());

            try {
                mController.await();
                System.out.print("VideoConference: All the participants have come.\n");
                System.out.print("VideoConference: Let's start...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private class Participant implements Runnable {
        private String mName;
        private VideoConference mVideoConference;

        public Participant(String name, VideoConference videoConference) {
            mName = name;
            mVideoConference = videoConference;
        }

        @Override
        public void run() {
            long duration = (long) (Math.random() * 10);

            try {
                TimeUnit.SECONDS.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            mVideoConference.arrive(mName);
        }
    }
複製代碼
相關文章
相關標籤/搜索