直接使用線程
new Thread(new Runnable() {
@Override
public void run() {
//耗時代碼
}
}).start();
這種方式的線程隨處可見,但是這種方式的寫法是存在一定問題的,我們知道,在操作系統中,線程是操作系統調度的最小單元,同時線程又不能無限制的產生,並且線程的創建和銷燬都會有資源的開銷,同時當線程頻繁的創建或者銷燬的時候,還會讓GC頻繁的運行,造成程序的卡頓,例如當我們需要網絡請求的時候,一定是講網絡請求的代碼放到子線程中去運行的,同時如果是ListView中圖片的畫,採用傳統的new
Thread的形式,會在ListView滑動的時候,一下開數十個子線程,程序就會卡頓起來;或者當我們進行下載的時候,通常會指定下載的優先級,優先級高的優先下載,優先級低的會暫停排隊,這種需求傳統的Thread也是做不到的。那麼這就需要用到線程池了。線程池簡介
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
我們來看看這幾個參數的意義:(以下的所說的任務可以理解爲實現Runnable接口的對象)corePoolSize
它指的是核心線程數,在默認情況下,核心線程會一直存活,即使他們是處於閒置狀態的,也就是說在默認狀態時,線程池中的核心線程會持續的存在,直至線程池的銷燬。而如果配置了allowCoreThreadTimeOut屬性爲true的話,那麼空閒的核心線程也會被銷燬了,當它空閒的時間超出了keepAliveTime這個參數規定的時間之後,它就會被銷燬掉。threadPoolExecutor.allowCoreThreadTimeOut(true);
值得注意的是,並不是說只有核心線程才能去執行任務,而是核心線程是最穩定的線程,在默認狀態下,它們不會銷燬,這樣在新的任務需要執行的時候,就會很節省時間,所以核心線程數只需要保證大於0就可以了。
maximumPoolSize
KeepAliveTime&TimeUnit
這兩個參數用來控制線程的存活時間,默認情況下只會作用於非核心線程,當線程池中的線程處於閒置狀態的時間超出了這兩個參數所設置的時間之後,線程池就會銷燬掉它,workQueue
線城池執行任務的規則
- 如果線程池中的線程數量沒有達到核心線程的數量,那麼會直接啓動一個核心線程來執行該任務。
- 如果線程池中的線程數量已經達到核心線程數,那麼任務就會被插入到任務隊列中排隊等待執行,當核心線程空閒的時候,就會從任務隊列中按照某種規則取出一個任務來執行
- 如果任務隊列滿了,或者由於其他原因,向線程池提交的任務不能插入到任務隊列中的時候,這個時候就會去看線程池中的線程數是否達到線程池的上限,如果沒有,就立即開啓一個線程並執行。
- 如果線程池中正在工作的線程數已經達到了線程池設置的上限,此時再向線程池中提交任務,線程池就會拒絕執行此任務
threadFactory
Thread newThread(Runnable r);
RejectedExecutionHandler handler
當線程池無法執行新任務,可能是由於任務隊列已滿或者其他問題,這時ThreadPoolExecutor就會調用handler的rejectedExecution方法來通知調用者,ThreadPoolExecutor爲RejectedExecutionHandler提供了四個可選值:CallerRunsPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
使用執行線程池的線程本身來運行該任務,此策略提供簡單的反饋控制機制,能減緩新任務的提交速度。AbortPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException();
}
這種策略直接拋出異常,丟棄任務DiscardPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
這種策略也是丟棄任務,不同的是,它並不會拋出異常DiscardOldestPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
拋棄舊的任務,在線程池沒有關閉的前提下,首先丟掉緩存在隊列中的最早的任務,然後重新嘗試運行該任務。並重復此過程,所有使用此策略的時候要額外小心~
四類線程池
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("線程:" + threadName + ",任務:" + index);
try {
//模擬耗時
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
我們創建了一個最大線程和核心線程都是3的一個線程池,然後向其中循環添加10個任務,每一個人只打印當前線程的名字,來看一下效果可以看到一開始就會執行3個任務,而後面的7個任務都會進入等待狀態當核心線程執行完一個之後,就會從隊列中按照FIFO的策略取出一個線程進行執行,所以除了前三個任務,剩下的任務是按照順序執行的
CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通過它的實例方法可以看出它的核心線程數是0也就是說該線程池並沒有核心線程,而它的最大線程數是int類型的的上限,那麼我們可以理解爲該線程池的最大線程數是沒有上限的,也就是說可以無限的創建線程。那麼當新任務向線程池中提交的時候,如果有空閒線程,就會把任務放到空閒線程中去,如果沒有空閒線程,就會開啓一個新的線程來執行此任務,而它的隊列SynchronousQueue是一個特殊的隊列,在多數情況下,我們可以把它簡單的理解爲一個無法插入的隊列,我們會在之後詳細說明的。 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 1; i <= 10; i++) {
final int index = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("線程:" + threadName + ",任務:" + index);
try {
long time = index * 500;
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
我們這次是在每一次添加的時候都會停止1s的時間,來看看CachedThreadPool的運行情況,並且,每一個任務所執行的時間也不一樣,效果如下ScheduledThreadPool
通過Executors的newScheduledThreadPool方法來創建。實例化代碼如下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());
}
可以看出它的核心線程數是固定的,而最大線程數也是int類型的上限,理解爲沒有限制,它與之前的線程池相區別的就是它的任務隊列,DelayedWorkQueue能讓任務週期性的執行,也就是說該線程池可以週期性的執行任務。 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//延遲2秒後執行該任務
System.out.println("任務開始");
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("任務:" + threadName );
}
}, 3, TimeUnit.SECONDS);
我們首先模擬延遲執行任務的代碼,在我們提交任務之前先打印一次任務開始,在提交任務的時候,去設置延遲時間爲3s,運行一下看看效果ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//延遲2秒後執行該任務
System.out.println("任務開始");
//延遲4秒後,每隔1秒執行一次該任務
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("執行任務");
}
}, 4, 1, TimeUnit.SECONDS);
我們設置了在提交任務時,需要延遲4s纔會第一次執行,同時在任務執行完畢後每隔1s又會重複的執行一次該任務,看一下效果SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
通過代碼可以看出,這種線程池,就是FixedThreadPool但是實例化方法的參數是1的嘛。總結:
任務隊列
SynchronousQueue:直接提交的隊列
DelayedWorkQueue:等待隊列
PriorityBlockingQueue:優先級隊列
ArrayBlockingQueue:有界的隊列
LinkedBlockingQueue&ArrayBlockingQueue
SynchronousQueue
PriorityBlockingQueue
public abstract class PriorityRunnable implements Runnable, Comparable<PriorityRunnable> {
private int priority;
public PriorityRunnable(int priority) {
if (priority < 0) {
throw new IllegalArgumentException();
}
this.priority = priority;
}
@Override
public int compareTo(PriorityRunnable another) {
return another.getPriority() - priority;
}
public int getPriority() {
return priority;
}
}
我們的抽象類最基本的需要實現Runnable接口才能被提交到線程池中,同時我們需要再實現Compareable接口,來告訴隊列到底誰大誰小,這裏我們自己寫了一個int類型的變量代表每一個任務的優先級,然後複寫compareTo方法來寫我們的比較條件,這裏要注意的是,當我們自己去寫的時候,不一定非要指定一個int類型的變量,也可以是其他的例如String等的,只要實現了Comparable接口就可以了。接下來我們就可以使用這個任務了,代碼如下ExecutorService priorityThreadPool = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>());
for (int i = 1; i <= 10; i++) {
final int priority = i;
priorityThreadPool.execute(new PriorityRunnable(priority) {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println("線程:" + threadName + ",正在執行優先級爲:" + priority + "的任務");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
我們創建了一個核心線程爲3的使用優先級隊列的線程池,向線程池中添加10個優先級不同的任務,來看看效果當我們運行的時候,前三個任務是沒有進入隊列的,至接進入到線程池的核心線程開始幹活了,之後的7個任務都會進入到優先級隊列,通過比較,再進入線程池工作的時候,就會讓線程按照我們設定好的優先級順序執行,優先級高的任務會先執行任務,如果我們讓每一個提交的任務都加入一個當前的時間,就可以完成類似LIFO的功能啦~
線程池的其他方法
afterExecute() :任務執行結束後執行的方法
terminated() :線程池關閉後執行的方法
public class MyThreadPool extends ThreadPoolExecutor {
public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
//在執行任務之前
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
//在執行任務之後
}
@Override
protected void terminated() {
super.terminated();
//線程池關閉
}
}
類似這樣,就可以在線程池執行任務之前和之後,都很方便的加上我們的功能信號量
在使用線程池的時候,任務的提交方式主要是由任務隊列決定的,但是任務隊列裏的方法比較多,複寫起來比較麻煩,而且官方也建議我們使用它提供給我們的幾種線程池,有時我們的隊列需要動態的調整FIFO和LIFO的策略,或者我們提交的策略非常複雜,系統默認的滿足不了我們怎麼辦,通常我們的做法是自己完全重新寫一個任務隊列,並不實現線程池的接口,也不作爲任務隊列參數放到線程池中,而我們所有的任務都會先提交到我們自己定義的這個隊列中來,然後當線程池空閒的時候再提交到線程池中去。那麼問題來了,我們怎麼知道線程池中是否有空閒的線程呢?這就要使用Semaphore 信號量了什麼是信號量
//只有一個位置的信號量,相當於停車場只有一個位置
final Semaphore semp = new Semaphore(2);
for(int i = 0;i<4;i++){
new Thread(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
//申請信號量
semp.acquire();
System.out.println(threadName+"申請到了信號量");
Thread.sleep(2000);
//釋放信號量
semp.release();
System.out.println(threadName+"釋放了了信號量");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
首先我們定義了一個只有2個位置的信號量,然後循環開啓了4個線程,而這4個線程可以理解爲幾乎是在同一時間開啓的,當線程開始的時候會嘗試申請信號量,如果申請成功就開始模擬一個耗時操作,在操作完成後,再釋放一次信號量,我們來看一看這幾個線程的運行情況自定義可以FIFO和LIFO的線程池
<LinearLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.lanou.chenfengyao.myapplication.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_FIFO"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FIFO"/>
<Button
android:id="@+id/btn_LIFO"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LIFO"/>
</LinearLayout>
<TextView
android:id="@+id/main_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</LinearLayout>
這個不用多說,就是2個Button來控制線程池的,一個TextView用來顯示當前的線程的public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button FIFOBtn, LIFOBtn;
private TextView mainTv;
private MyThreadPool myThreadPool;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FIFOBtn = (Button) findViewById(R.id.btn_FIFO);
LIFOBtn = (Button) findViewById(R.id.btn_LIFO);
mainTv = (TextView) findViewById(R.id.main_text);
FIFOBtn.setOnClickListener(this);
LIFOBtn.setOnClickListener(this);
handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//設置給TextView;
mainTv.setText("任務:"+msg.what);
return false;
}
});
myThreadPool = new MyThreadPool(1);
for (int i = 0; i < 100; i++) {
final int index = i;
myThreadPool.execute(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage(index);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_FIFO:
myThreadPool.setWay(MyThreadPool.OutWay.FIFO);
break;
case R.id.btn_LIFO:
myThreadPool.setWay(MyThreadPool.OutWay.LIFO);
break;
}
}
}
我直接在onCreate方法裏創建了我自定義的一個線程池,然後向裏面添加了100個任務,而這個線程池的核心線程和最大線程我都設置成了1個,每一個任務也就是把自己任務號通過handler發送給主線程,然後顯示到TextView上,在按鈕的監聽裏,調用自定義線程池的setWay方法,把FIFO還是LIFO的信息設置上,那麼關鍵的代碼就在我們自定義的線程池裏了,我們來看一下:package com.lanou.chenfengyao.myapplication;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by ChenFengYao on 16/5/17.
*/
public class MyThreadPool extends ThreadPoolExecutor {
private volatile Semaphore semaphore;
private List<Runnable> runnableList;
private LoopThread loopThread;
private boolean flag;
//兩種策略,先進先出和先進後出
enum OutWay {
FIFO, LIFO
}
private OutWay outWay;
public MyThreadPool(int corePoolSize) {
super(corePoolSize, corePoolSize, 0l, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
semaphore = new Semaphore(corePoolSize);
runnableList = new LinkedList<>();
flag = true;
outWay = OutWay.FIFO;//默認是先進先出
loopThread = new LoopThread();
loopThread.start();
}
//提交任務的方法
@Override
public synchronized void execute(Runnable command) {
//所有來的任務是提交到我們自己的任務隊列中
runnableList.add(command);
if (runnableList.size() < 2) {
//如果這是隊列中的第一個任務,那麼就去喚醒輪詢線程
synchronized (loopThread) {
loopThread.notify();
}
}
}
//設置是FIFO/LIFO
public void setWay(OutWay outWay) {
this.outWay = outWay;
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
//任務完成釋放信號量
semaphore.release();
}
@Override
protected void terminated() {
super.terminated();
flag = false;//輪詢線程關閉
}
class LoopThread extends Thread {
@Override
public void run() {
super.run();
while (flag) {
if (runnableList.size() == 0) {
try {
//如果沒有任務,輪詢線程就等待
synchronized (this) {
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
//請求信號量
semaphore.acquire();
int index = runnableList.size();
switch (outWay) {
case FIFO:
//先進先出
index = 0;
break;
case LIFO:
//先進後出
index = runnableList.size() - 1;
break;
}
//調用父類的添加方法,將任務添加到線程池中
MyThreadPool.super.execute(runnableList.get(index));
runnableList.remove(index);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
可以看到,這個自定義的線程池的核心線程和最大線程都是一樣的,通過構造方法傳進來的,這裏是1,同時,我們將我們信號量的數值也設置爲核心線程數,並且我們在內部有一個List用來存放我們提交的任務,我將線程池父類的execute方法複寫了,這裏不再向線程池內提交,而是存放到我們自己的RunnableList裏。關閉線程池
最後
Runtime.getRuntime().availableProcessors();
來獲得