要求:熟練使用線程、線程池和同步線程java
在 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 是一個輕量級的異步任務類,它能夠在線程池中執行後臺任務,而後把執行的進度和結果傳遞給主線程,而且在主線程中更新 UI。多線程
AsyncTask 是一個抽象類,public abstract class AsyncTask<Params, Progress, Result>併發
其中,參數1 Params:異步任務的入參;參數2 Progress:執行任務的進度;參數3 Result:後臺任務執行的結果。dom
還提供了四個核心方法:異步
簡單使用: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 是一個實現了 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
線程池的優勢:
ExecutorService 是最基礎的線程池接口,ThreadPoolExecutor 類是對線程池的具體實現。下面經過經常使用的構造函數來分析一下用法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
複製代碼
先解釋參數:
線程池的分配遵循以下規則:
下面四種線程池都是直接或者間接使用 ThreadPoolExecutor 實現
經過 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");
}
});
}
複製代碼
經過 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");
}
});
}
複製代碼
經過 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");
}
});
}
複製代碼
經過 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(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
}
複製代碼
wait 方法:線程自動釋放佔有的對象鎖,並等待 notify; notify 方法:喚醒一個正在 wait 當前對象鎖的線程,並讓他拿到對象鎖; notifyAll 方法:喚醒全部正在 wait 當前對象鎖的線程。
notify 和 notifyAll 的區別:notify 是喚醒一個正在 wait 當前對象鎖的線程,而 notifyAll 是喚醒全部。notify 是本地方法,具體喚醒的是哪個線程,由 java 虛擬機定,notifyAll 喚醒的線程只是跳出 wait 狀態,接下來它們還會競爭對象鎖。
這三個方法是 Object 自帶的,而且在調用這三個方法時須要得到這個對象的鎖,不然就會報錯:
java.lang.IllegalMonitorStateException:current thread not owner
複製代碼
這三個方法以下:
參考:java中的wait、notify、notifyAll
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);
}
}
複製代碼