Android
中因爲主線程不能進行耗時操做,因此耗時操做都要放到子線程中去作,因此多線程開發在實際中幾乎沒法避免。這篇文章就來總結一下與多線程有關的基礎知識。java
一個線程有如下幾種狀態:
1. New: 新建立狀態。線程被建立還沒被調用start方法。在線程運行前還有些基礎工做要作。
2. Runnable: 可運行狀態。調用過start方法。一個可運行的線程可能正在運行也可能沒在運行。
3. Blocked: 阻塞狀態。表示線程被鎖阻塞,暫時不活動。
4. Waiting: 等待狀態。線程暫時不活動,直到線程調度器從新激活它。
5. Time waiting: 超時等待狀態。和等待狀態不用的是它是能夠在指定的時間自行返回。
6. Terminated: 終止狀態。表示當前線程已經執行完畢。android
各個狀態之間的轉換關係以下圖。 編程
Java
中建立線程的方法有三種:數組
class MyThread extends Thread {
@Override
public void run() {
Log.d(TAG, "MyThread線程名:" + Thread.currentThread().getName());
Log.d(TAG, "MyThread is running");
}
}
private void createThread() {
MyThread myThread = new MyThread();
myThread.start();
}
複製代碼
class MyRunnable implements Runnable {
@Override
public void run() {
Log.d(TAG, "MyRunnable線程名:" + Thread.currentThread().getName());
Log.d(TAG, "MyRunnable is running");
}
}
private void createRunnable() {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
複製代碼
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Log.d(TAG, "MyCallable線程名:" + Thread.currentThread().getName());
Log.d(TAG, "MyCallable is running");
return "callable return";
}
}
private void createCallable() throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
//獲取返回值
String result = futureTask.get();
Log.d(TAG, result);
}
複製代碼
Thread
和Runnable
平時見得不少了,而關於Callable
相對比較少,它與Runnnable
接口相似,可是它有兩點與Runnnable
接口不一樣:緩存
Runnnable
中的run
方法沒法拋出異常,而Callable
的call
方法能夠。Runnnable
中的run
方法沒有返回值,而Callable
的call
方法能夠。Callable
能夠拿到一個Future
對象,表示異步任務的返回結果,調用get
方法能夠獲取結果,若是此時異步任務還沒完成會阻塞當前進程直到返回結果。下面舉個Callable
使用的例子,模擬一個買菜作菜的過程。先用Runnable
實現,再用Callable
實現,作一下對比。安全
private void runnableCook() throws InterruptedException {
//買食材線程
Thread foodThread = new Thread(new Runnable() {
@Override
public void run() {
try {
//買食材耗時
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "五花肉已買");
food = new String("五花肉");
}
});
Log.d(TAG, "我要開始作菜了");
Log.d(TAG, "我準備作紅燒肉");
Log.d(TAG, "我沒有食材");
//開啓購買食材線程
Log.d(TAG, "叫隔壁老王幫我出門去買食材");
foodThread.start();
//模擬清洗廚具準備的耗時操做
Log.d(TAG, "清洗廚具準備調料,準備作菜");
Thread.sleep(3000);
//要等到食材買回來才能開始作菜,因此將食材線程join
foodThread.join();
Log.d(TAG, "開始作菜了");
cook(food);
}
private void cook(String food) {
if (TextUtils.isEmpty(food)) {
//沒調用foodThread.join就會走這裏
Log.d(TAG, "沒有食材無法作菜");
return;
}
Log.d(TAG, "菜作好了,開吃了");
}
複製代碼
代碼很簡單,就是定義一個買菜的線程作買菜的耗時任務,而後主線程打印作菜日誌,由於Runnable
沒有返回值,在作完作菜準備以後須要調用foodThread.join()
等待食材買回,再進行cook
工做,若是不等到foodThread
工做結束就調用cook
方法,就會發現沒有食材作菜。bash
接下來使用Callable
來完成這個過程。多線程
private void callableCook() throws InterruptedException, ExecutionException {
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(5000);
Log.d(TAG, "五花肉已買");
return "五花肉";
}
};
FutureTask<String> futureTask = new FutureTask<String>(callable);
Thread foodThread = new Thread(futureTask);
Log.d(TAG, "我要開始作菜了");
Log.d(TAG, "我準備作紅燒肉");
Log.d(TAG, "我沒有食材");
Log.d(TAG, "叫隔壁小王出門去買食材");
foodThread.start();
Log.d(TAG, "清洗廚具準備調料,準備作菜");
Thread.sleep(3000);
if (!futureTask.isDone()) {
Log.d(TAG, "食材還沒到啊,小王好慢啊");
}
food = futureTask.get();
Log.d(TAG, "開始作菜了");
cook(food);
}
private void cook(String food) {
if (TextUtils.isEmpty(food)) {
//沒調用foodThread.join就會走這裏
Log.d(TAG, "沒有食材無法作菜");
return;
}
Log.d(TAG, "菜作好了,開吃了");
}
複製代碼
Callable
接口的使用方法和Runnable
相似。併發
第一步先實現Callable
接口併爲其指定一個泛型,這個泛型類型就是call
方法要返回的類型,實現call
方法在其中作耗時任務,並將任務結果經過return
返回。app
第二步建立一個FutrueTask
,一樣設置返回類型泛型,並將剛實現的Callable
對象傳入,這個FutrueTask
後面就用來得到異步任務的結果。
最後一步和Runnable
同樣,新建一個Thread
對象,將FutrueTask
傳入,而後開啓線程便可。
與Runnable
不一樣的是FutrueTask
提供一個isDone
方法用來判斷異步任務是否完成,而且提供futureTask.get
方法來獲取異步任務的返回結果,若是此時任務還沒結束,則會阻塞當前線程直到任務完成。這樣就不會出現食材還沒買回來就開始作菜了。
一個線程的run
方法運行完成或者方法中剛出現異常以後這個線程就會終止。除此以外Thread
類提供了stop
和interrupt
方法終止線程,其中stop
已過期被棄用,而調用interrupt
方法,線程會將本身的終止標識爲true
,線程會一直檢測這個標識位以判斷是否被終止。
private volatile boolean flag = true;
private void createFlagThread() {
flag = true;
count = 0;
thread = new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
Log.d(TAG, "flag count:" + count++);
}
}
});
thread.start();
}
private void changeFlag() {
flag = false;
}
複製代碼
定義布爾值flag
調用createFlagThread
方法建立線程,run
方法中經過flag
來判斷是否結束循環從而結束線程,當須要中止線程時,調用changeFlag
方法將布爾值設置爲false
便可。
在3.4的例子中,用來中止循環的布爾值變量上加了一個volatile
關鍵字,volatile
被稱爲輕量級的synchronized
,但volatile
只能修飾變量不能修飾方法。在遇到變量須要被多個線程訪問時能夠用volatile
關鍵字修飾它。例如在經典的雙重檢查的單例模式下就會用到volatile
修飾。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
複製代碼
那麼volatile
真的能替代synchronized
嗎?答案確定是不能的。關於volatile
的做用先要從併發編程的三個特性和Java
的內存模型開始講起。
// 例如Java中的賦值操做
int i = 1;//是原子行操做
int j = i;//不是原子性操做,由於這個操做要先讀取i的值,再將i的值賦給j
複製代碼
Java
內存模型中是容許編譯器和處理器對指令進行重排序的,重排序不會影響單線程執行的正確性,可是在多線程狀況下就會形象多線程併發的正確行。Java
運行時數據區域以下圖。
從圖中能夠看出主要分爲如下幾個部分:
1. 程序計數器
程序計數器是一塊較小的內存空間。可看作當前線程所執行的字節碼的行號指示器,線程私有。
2. Java虛擬機棧
線程私有。生命週期與線程相同。Java
虛擬機棧描述的是Java
方法執行的內存模型。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
3. 本地方法棧
與虛擬機棧做用類似,區別是虛擬機棧爲虛擬機執行Java
方法也就是字節碼服務,而本地方法棧爲虛擬機使用到的Native
方法服務。
4. Java堆
全部線程共享的一塊內存區域。在虛擬機啓動時建立,存放對象實例。是垃圾收集器管理的主要區域。
5. 方法區
線程共享的一塊內存區域。用於存儲虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼的數據。
6. 運行時常量池
是方法區的一部分。用於存放編譯期生成的各類字面量和符號引用。具備動態性。
7. 直接內存
並非虛擬機運行時數據區的一部分,也不是Java
虛擬機規範中定義的內存區域,可是這部份內存被頻繁使用。
從以上能夠看出Java
堆內存是被全部線程共享的內存區域,因而就存在可見性的問題。Java
內存模型定義了線程和主存之間的抽象關係:線程之間共享變量存儲在主存中,每一個線程有個私有的本地內存,本地內存中存儲了共享變量的一個副本(本地內存是Java
內存模型中一個抽象的概念,不真實存在)。抽象示意圖以下。
如上圖,線程A
要與線程B
通訊要通過兩個步驟:一是線程A
把線程A
本地內存中更新過的共享變量刷新到主內存中去;二是線程B
要到主線程中讀取線程A
更新過去的共享變量。
回到以前講的volatile
關鍵字,volatile
關鍵字修飾的變量只能保證併發三個特性中的兩個:可見性和有序性。並不能保證原子性。因此在以前不管是單例仍是停止線程的代碼中都將多個線程訪問的變量使用了volatile
修飾,保證變量在一個線程修改了以後對全部線程可見且指令有序。又由於沒法保證原子性,因此即便聲明變量使用了volatile
修飾,可是多個線程併發執行例如i++
這種非原子性操做是仍是會出現問題,這時就須要使用synchronized
等方法來解決線程的同步問題。
多線程訪問同一個資源必然會產生線程同步問題,不解決線程同步問題就會形成資源數據的錯誤。關於線程同步問題仍是經過那個經典的多窗口賣票問題來解釋理解。
m
個賣票窗口同時賣n
張票,採用多線程實現。這裏先看一下不作同步的錯誤示例。這裏畫了個簡單的界面,兩個輸入框輸入總票數和窗口數,按鈕點擊開始賣票,最後的結果由TextView
展現。
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".TicketActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/et_ticket_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="輸入總票數"
android:inputType="number"
android:text="100" />
<EditText
android:id="@+id/et_window_count"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="輸入賣票窗口數"
android:inputType="number"
android:text="3" />
<Button
android:id="@+id/btn_begin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="開賣" />
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:textSize="16sp" />
</LinearLayout>
</ScrollView>
複製代碼
賣票方法代碼:
//結果字符串
private StringBuilder result = new StringBuilder();
//總票數
private int allTicketCount = 0;
//賣票窗口數
private int windowCount = 0;
private void begin() {
allTicketCount = Integer.parseInt(mEtTicketCount.getText().toString().trim());
windowCount = Integer.parseInt(mEtWindowCount.getText().toString().trim());
//每次調用清空以前結果
result.delete(0, result.length());
result.append("總票數:" + allTicketCount + ",共有" + windowCount + "個窗口賣票\n");
mTvResult.setText(result.toString());
//循環建立多個窗口線程並開啓
for (int count = 1; count <= windowCount; count++) {
Thread window = new Thread(new TicketErrorRunnable(), "售票窗口" + count);
window.start();
}
}
/**
* 無同步
*/
class TicketErrorRunnable implements Runnable {
@Override
public void run() {
while (allTicketCount > 0) {
// 總票數大於0就將票數減一
allTicketCount--;
// 輸出添加賣出一張票和剩餘票數
result.append(Thread.currentThread().getName() + ":賣出一張票,還剩" + allTicketCount + "張票。\n");
//通知刷新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//循環結束說明票賣完了,刷新UI
result.append(Thread.currentThread().getName() + ":票賣完啦。\n");
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
}
}
複製代碼
運行結果:
begin
方法,根據運行結果能夠看到不作線程同步,會出現這種兩個窗口賣出同一張票的狀況致使數據的錯誤。
將須要同步的代碼放到同步代碼塊中能夠保證線程同步,具體來看代碼。
/**
* 同步代碼塊
*/
class TicketSyncRunnable implements Runnable {
@Override
public void run() {
while (allTicketCount > 0) {
// ----------同步代碼塊----------------
synchronized (TicketActivity.class) {
allTicketCount--;
result.append(Thread.currentThread().getName() + ":賣出一張票,還剩" + allTicketCount + "張票。\n");
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
}
// ----------同步代碼塊----------------
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
result.append(Thread.currentThread().getName() + ":票賣完啦。\n");
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
}
}
複製代碼
這裏其它的都沒改變,只修改了Runnable
中的內容,將對總票數的計算放到同步代碼塊中進行同步,保證同時只有一個線程能對總票數allTicketCount
進行操做。
運行結果:
將須要同步的代碼抽出一個方法,再方法上加上synchronized
關鍵字成爲同步方法,也能夠保證線程同步。
/**
* 同步方法
*/
class TicketSyncMethodRunnable implements Runnable {
@Override
public void run() {
ticket();
result.append(Thread.currentThread().getName() + ":票賣完啦。\n");
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
}
// 同步方法
private synchronized void ticket() {
while (allTicketCount > 0) {
allTicketCount--;
result.append(Thread.currentThread().getName() + ":賣出一張票,還剩" + allTicketCount + "張票。\n");
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
複製代碼
將對總票數的操做抽出到同步方法中,查看運行結果:
本身給代碼加鎖固然也能夠保證進程同步。
private ReentrantLock reentrantLock = new ReentrantLock();
/**
* 重入鎖
*/
class TicketLockRunnable implements Runnable {
@Override
public void run() {
while (allTicketCount > 0) {
//上鎖
reentrantLock.lock();
try {
allTicketCount--;
result.append(Thread.currentThread().getName() + ":賣出一張票,還剩" + allTicketCount + "張票。\n");
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
} finally {
//開鎖
reentrantLock.unlock();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
result.append(Thread.currentThread().getName() + ":票賣完啦。\n");
runOnUiThread(new Runnable() {
@Override
public void run() {
mTvResult.setText(result.toString());
}
});
}
}
複製代碼
運行結果:
Android
中固然可使用上面提到的三種建立線程的方法實現多線程,可是我這裏要說的是Android
裏爲咱們提供的幾種封裝過的多線程執行異步任務的使用方法,畢竟咱們手動new Thread().start()
不只不便於管理,並且還會有意想不到的收穫(內存泄漏)。
AsyncTask
是Android
提供的執行異步任務的類。
AsyncTask
的使用:首先定義一個類繼承AsyncTask
:
class MyAsyncTask extends AsyncTask<String, Integer, String> {
int count = 0;
@Override
protected void onPreExecute() {
//doInBackground執行以前
Log.d(TAG, Thread.currentThread().getName() + " 異步任務準備開始 " + System.currentTimeMillis());
}
@Override
protected void onProgressUpdate(Integer... values) {
//doInBackground執行中
Log.d(TAG, Thread.currentThread().getName() + " 任務進行中:" + values[0] + "% ");
}
@Override
protected void onPostExecute(String result) {
//doInBackground執行以後
Log.d(TAG, Thread.currentThread().getName() + " " + result + " " + System.currentTimeMillis());
}
@Override
protected String doInBackground(String... strings) {
Log.d(TAG, Thread.currentThread().getName() + " 異步任務執行中 " + System.currentTimeMillis());
while (count < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count += 10;
publishProgress(count);
}
Log.d(TAG, Thread.currentThread().getName() + " 異步任務完成!" + System.currentTimeMillis());
return Thread.currentThread().getName() + ":任務完成";
}
}
複製代碼
AsyncTask
三個泛型參數Params
、Progress
、Result
分別對應傳入的參數類型、中間進度的類型、返回結果的類型。AsyncTask
主要有四個回調方法複寫。
doInBackground
前被調用,進行耗時操做前的初始化或者準備工做,運行在主線程。doInBackground
執行完成後調用,返回處理結果,更新UI
等,運行在主線程。doInBackground
方法中調用publishProgress
方法後調用更新耗時任務進度,運行在主線程。調用AsyncTask
:
MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
複製代碼
運行結果日誌:
關於想進一步瞭解AsyncTask
的源碼運行原理能夠看Android進階知識:AsyncTask相關
HandlerThread
是將Handler
與Thread
的封裝,本質上仍是一個Thread
。
HandlerThread
的使用以下:
// 建立一個HandlerThread
HandlerThread handlerThread = new HandlerThread("myHandlerThread");
//調用start方法開啓線程
handlerThread.start();
//建立一個Handler傳入handlerThread的Looper
handler = new Handler(handlerThread.getLooper()) {
//複寫handleMessage方法處理消息
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, Thread.currentThread().getName() + " receive one message");
break;
}
}
};
//在調用處使用handler發送消息
Message message = Message.obtain();
message.what = 1;
handler.sendMessage(message);
// 使用完退出HandlerThread
handlerThread.quit();
複製代碼
運行結果日誌:
HandlerThread
的源碼運行原理能夠看
Android進階知識:HandlerThread相關
IntentService
是將Service
與HandlerThread
封裝,與普通Service
不一樣的是,普通Service
運行在主線程,IntentService
建立一個工做子線程執行任務,而且IntentService
在執行完任務後自動關閉服務,而普通Service
須要手動調用stopService
方法。 IntentService
使用以下:
//繼承實現IntentService
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
//複寫onHandleIntent方法根據intent傳遞參數實現處理任務
String type = intent.getStringExtra("type");
switch (type){
case "type1":
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG,Thread.currentThread().getName()+" type1 doing");
break;
}
}
}
//由於是服務因此要在AndroidManifest中註冊
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.thread.threaddemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
......
<service android:name=".MyIntentService"/>
</application>
</manifest>
//啓動服務
Intent intent = new Intent(this, MyIntentService.class);
intent.putExtra("type", "type1");
startService(intent);
複製代碼
運行日誌結果:
IntentService
的源碼運行原理能夠看
Android進階知識:IntentService相關
對於Android
開發者來講,Handler
再熟悉不過了,Handler
是Android
提供的一種多線程間通訊方式。Android
關於多線程確定避不開Handler
。這裏對Handler
的使用就不舉例了,想要了解使用及原理的能夠看我寫的這篇Android進階知識:Handler相關。
線程池是用來管理線程的工具,在開發中若是每次進行異步任務都手動建立一個線程,不只浪費資源並且不容易管理控制,線程池的使用就能很好的解決這些問題。
在瞭解線程池以前先來了解一下阻塞隊列,阻塞隊列的理解有助於咱們對線程池的工做原理的學習。阻塞隊列是併發編程中「生產者-消費者」模式裏用來存放元素的容器,生產者生產元素放入阻塞隊列,消費者從阻塞隊列中取出元素,當隊列中沒有元素或者隊列中元素已滿時會發生阻塞,直到隊列中再次加入元素或者去除元素爲止。阻塞隊列實現了元素添加取出時的鎖操做,因此使用時無需單獨考慮線程同步問題。
Java
中提供了七種阻塞隊列:
下面經過使用阻塞隊列實現一個「生產者-消費者」模式的例子。
package com.thread.threaddemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "BlockingQueueActivity";
private Button mBtnMain;
private Button mBtnProduce;
private Button mBtnConsumer;
// 阻塞隊列
private ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(5);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blocking_queue);
initView();
}
private void initView() {
mBtnMain = (Button) findViewById(R.id.btn_main);
mBtnProduce = (Button) findViewById(R.id.btn_produce);
mBtnConsumer = (Button) findViewById(R.id.btn_consumer);
mBtnMain.setOnClickListener(this);
mBtnProduce.setOnClickListener(this);
mBtnConsumer.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_main:
mainStart();
break;
case R.id.btn_produce:
addData();
break;
case R.id.btn_consumer:
removeData();
break;
}
}
/*
* 手動從阻塞隊列取出一個元素
*/
private void removeData() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
queue.take();
printData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "remove");
thread.start();
}
/*
* 手動從阻塞隊列添加一個元素
*/
private void addData() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
queue.put("data");
printData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "add");
thread.start();
}
/*
* 打印日誌
*/
private void printData() {
Log.d(TAG, Thread.currentThread().getName() + ":" + queue.toString());
}
/*
* 開啓生產者消費者線程
*/
private void mainStart() {
ProducerThread producerThread = new ProducerThread("Producer");
producerThread.start();
ConsumerThread consumerThread = new ConsumerThread("Consumer");
consumerThread.start();
}
/*
* 消費者線程
*/
class ConsumerThread extends Thread {
public ConsumerThread(String name) {
super(name);
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
queue.take();
printData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
* 生產者線程
*/
class ProducerThread extends Thread {
public ProducerThread(String name) {
super(name);
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
queue.put("data");
printData();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
複製代碼
運行打印日誌:
從這段代碼能夠看到首先建立阻塞隊列queue
容量爲5,點擊按鈕調用mainStart
方法,開啓生產者線程和消費者線程,生產者線程每隔1秒生產一個元素加入隊列,消費者每隔2秒消費一個元素取出隊列。能夠看到因爲生產比消費快,因此容器逐漸被加滿,最後保證隊列中充滿五個元素,再想添加只能等到消費者線程消費了元素才行。固然也能夠經過手動調用addData
或removeData
方法來向隊列裏添加和取出元素,一樣阻塞隊列滿了或者空了就會發生阻塞,直到隊列中空出位置或者加入新元素爲止。
Java
中線程池的核心實現類是ThreadPoolExecutor
,不管是Runnable
仍是Callable
均可以交給線程池來統一管理,要使用線程池就要先建立一個線程池對象。
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());
複製代碼
這樣就建立了一個線程池對象,接着就能夠調用execute
或submit
提交任務,線程池會分配線程處理任務。不過ThreadPoolExecutor
的構造方法中這幾個參數到底表明了什麼,咱們來看一下它的構造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
複製代碼
構造函數中就是對這幾個成員參數的一個初始化賦值,來看這幾個參數表明的意思。
corePoolSize
數,就會建立新線程來處理任務。maximumPoolSize
時,線程池仍舊會建立新的線程來處理任務。keepAliveTime
的時間單位。線程執行流程源碼從ThreadPoolExecutor
的execute
方法開始來看。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//判斷工做線程數是否小於核心線程數
if (workerCountOf(c) < corePoolSize) {
//小於就調用addWorker方法建立核心線程
//這裏第二個參數true表示是核心線程
if (addWorker(command, true))
return;
c = ctl.get();
}
//不然調用workQueue.offer方法將任務放入任務隊列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//放入任務隊列失敗則再調用addWorker方法建立非核心線程執行任務
//這裏第二參數傳入false表示非核心線程
else if (!addWorker(command, false))
//若是addWorker返回false
//說明線程數超過最大線程數maximumPoolSize
//調用reject方法執行飽和策略
reject(command);
}
複製代碼
由上述代碼和註釋能夠看出具體執行流程以下:
corePoolSize
,若沒有到達核心線程數就建立核心線程處理任務。maxmumPoolSize
,若未達到最大線程數,則建立非核心線程處理任務。這裏再繼續看一下reject
方法:
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
複製代碼
調用了飽和策略中的rejectedExecution
方法。這裏飽和策略有4種,分別是:
RejectedExecutionException
異常。這裏看下默認的AbordPolicy
的實現。
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
複製代碼
能夠看到實現很簡單就是實現了RejectedExecutionHandler
接口,複寫rejectedExecution
方法,拋出RejectedExecutionException
異常。其它的飽和策略的實現也是相似。
Java
中提供了幾種默認經常使用線程池。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
複製代碼
從建立方法中能夠看出FixedThreadPool
線程池的核心線程數和最大線程數是相同的,因此它是沒有非核心線程的。而且它的線程空閒超時時長設置爲0,因此一旦線程任務結束空閒下來就會被回收,所以不會有空閒線程存在。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
複製代碼
CachedThreadPool
它的核心線程數爲0,不會建立核心線程,而是直接將任務加入任務隊列,而它的最大線程數爲Integer.MAX_VALUE
,也就是說能夠無限建立非核心線程的來處理任務。而且它的線程空閒超時時間爲60秒,空閒線程能夠緩存60秒後纔會被回收。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
複製代碼
SingleThreadExecutor
的核心線程數爲1,最大線程數爲1,因此SingleThreadExecutor
線程池中只有一個核心線程在工做,空閒超時時間爲0,任務結束當即回收,在惟一的核心線程在工做時,提交的任務會放入LinkedBlockingQueue
任務隊列中等待一個一個執行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
複製代碼
ScheduledThreadPool
的核心線程數是固定傳入的,而最大線程數是Integer.MAX_VALUE
,任務隊列爲DelayedWorkQueue
,自己是無界隊列,因此線程池中不會建立非核心線程,當工做線程數到達核心線程數時,線程池只會一直往任務隊列中添加任務,空閒線程的超時時間爲10秒。
以上就是Android
中多線程相關的基礎知識。關於多線程在使用的時候須要多加註意,不只要注意保證線程同步在Android
中還要注意內存泄漏的發生。須要頻繁建立子線程操做最好使用線程池進行管理。