線程經常使用類和接口以下:java
1.Thread和Runnable是最經常使用的,區別是一個是類,一個是接口。類的缺點是隻能單繼承,而接口沒有這個限制。編程
2.Thread和Runnable沒有返回值,想獲得線程執行結果,要麼是經過構造器事先注入存儲執行結果的對象,要麼是經過生產者-消費者模式獲得。爲了直接獲得線程執行結果,java併發包增長了Callable,不過Callable須要經過ExecutorService來提交執行,而不能簡單經過Thread注入後執行。緩存
3.Future是線程池中線程執行後的返回結果,另外經過它能夠終止線程執行,判斷線程的完成狀態等等。安全
4.一般狀況下,使用以上幾個接口和類已經足夠,FutureTask做爲補充,使得能夠不用走左邊線程池那條線,就能執行線程並獲得線程執行結果,能夠理解爲是一種輕量級的調用方式,它的調用過程以下:bash
FetureTask task = new FutureTask(new Callable()); //這裏的Callable須要自行實現
Thread thread = new Thread(task); //這裏能夠看到FetureTask實現Runnable接口的目的:能被看成線程任務執行。
V v = task.get(); //這裏能夠看到FetureTask實現Future接口的目的:獲得線程任務執行結果。
複製代碼
5.Executors是線程池工廠,生成不一樣類型的線程池。Executors可能引發內存溢出,有的大廠不推薦使用,而是推薦經過ThreadPoolExecutor人工建立線程池。併發
6.ExecutorService是線程執行服務,負責執行/提交線程任務。dom
7.ThreadPoolExecutor用於人工建立線程池,同時也能夠執行/提交線程任務。函數
以上就是線程經常使用的類和接口,每一個都寫個demo練習一下,很快就能掌握,使用時也能瞭然於胸。post
雖然有的大廠不推薦使用Executors,甚至做爲一項代碼檢查規則給予提示,可是根據實際業務場景須要,若是沒有致使內存溢出的場景,仍是能夠用的。優化
能夠經過Executors.newFixedThreadPool(nThreads:int)方法來建立固定大小的線程池,而後ExecutorService來提交要執行的線程任務,這裏因爲沒有限制ExecutorService能提交多少任務,所以可能致使提交任務太多而致使內存溢出。代碼示例:
public class FixedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made rice.");
}
});
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made dish.");
}
});
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " clean up table.");
}
});
executorService.shutdown();
}
}
複製代碼
輸入日誌:
pool-1-thread-1 made rice.
pool-1-thread-2 made dish.
pool-1-thread-2 clean up table.
Process finished with exit code 0
複製代碼
日誌能夠看出,線程池裏有兩個線程,總共3個任務被執行。
能夠經過Executors.newSingleThreadExecutor()來快速建立只有一個線程的線程池,提交到這個線程池裏的多個任務只能按照順序一個個的執行。代碼示例:
public class SingleThreadDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made rice.");
}
});
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made dish.");
}
});
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " clean up table.");
}
});
executorService.shutdown();
}
}
複製代碼
輸出日誌:
pool-1-thread-1 made rice.
pool-1-thread-1 made dish.
pool-1-thread-1 clean up table.
Process finished with exit code 0
複製代碼
能夠只有一個線程執行多個任務。
線程池中的任務能夠按照計劃執行,代碼示例以下:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/** * @ClassName ScheduledThreadPoolDemo * @Description 按計劃執行任務線程池demo * @Author 鏗然一葉 * @Date 2019/10/11 16:13 * @Version 1.0 * javashizhan.com **/
public class ScheduledThreadPoolDemo {
public static long fixedRateInterval = 0;
public static long lastFixedRateRunTime = System.nanoTime();
public static long withFixedInterval = 0;
public static long lastWithFixedRunTime = System.nanoTime();
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
//1秒後執行,只執行1次
executorService.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made rice.");
}
}, 1, TimeUnit.SECONDS);
//1秒後開始執行,每間隔3秒執行一次,這個間隔時間是從前一個任務【執行開始時間】算起
lastFixedRateRunTime = System.nanoTime();
executorService.scheduleAtFixedRate(new Runnable() {
public void run() {
long runTime = System.nanoTime();
fixedRateInterval = runTime - lastFixedRateRunTime;
lastFixedRateRunTime = runTime;
//模擬任務執行
sleep(2); //這個休眠時間不影響下次執行間隔時間的計算,執行間隔是3秒(當任務執行耗時小於3秒時,若是大於3秒了,則間隔爲任務的執行耗時)
System.out.println(Thread.currentThread().getName() + " made dish. Interval:" + fixedRateInterval);
}
}, 1, 3,TimeUnit.SECONDS);
//兩秒後執行任務,每次任務之間間隔3秒,這個間隔時間是從前一個任務【執行結束時間】算起
lastWithFixedRunTime = System.nanoTime();
executorService.scheduleWithFixedDelay(new Runnable() {
public void run() {
long runTime = System.nanoTime();
withFixedInterval = runTime - lastWithFixedRunTime;
lastWithFixedRunTime = runTime;
//模擬任務執行
sleep(2); //這個休眠時間會影響下次執行間隔時間的計算,執行間隔是2秒加上本次運行時間
System.out.println(Thread.currentThread().getName() + " clean up table. Interval:" + withFixedInterval);
}
}, 2, 3, TimeUnit.SECONDS);
}
public static void sleep(long millis) {
try {
TimeUnit.SECONDS.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
輸出日誌:
pool-1-thread-1 made rice.
pool-1-thread-2 made dish. Interval:1000771200
pool-1-thread-3 clean up table. Interval:2002448300
pool-1-thread-2 made dish. Interval:3000158800
pool-1-thread-2 made dish. Interval:2999740199
pool-1-thread-1 clean up table. Interval:5001442200
pool-1-thread-2 made dish. Interval:3000925301
pool-1-thread-3 clean up table. Interval:5000781199
pool-1-thread-1 made dish. Interval:2999058400
pool-1-thread-1 made dish. Interval:3000752800
pool-1-thread-2 clean up table. Interval:5001498901
pool-1-thread-1 made dish. Interval:2999442900
pool-1-thread-1 made dish. Interval:3001681600
pool-1-thread-3 clean up table. Interval:5001714800
複製代碼
能夠看到:
1.made rice執行了一次
2.clean up table首次2秒後執行,後續間隔爲5秒(任務間隔時間+任務執行時間)
3.made dish首次在1秒後執行,後續間隔爲3秒(任務間隔時間,沒有加上任務執行時間,而且任務執行時間小於間隔時間,因此按照間隔時間執行)
經過Executors.newCachedThreadPool()方法能夠建立可緩存的線程池,默認池大小爲0,最大值爲Integer.MAX_VALUE,若是有線程超過60秒未使用就會自動釋放。
Executors的每個建立線程池的方法都是經過ThreadPoolExecutor來構造的,建立緩存線程池調用的構造函數以下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
複製代碼
下一章節將介紹ThreadPoolExecutor,這裏就再也不贅述。
ThreadPoolExecutor手動建立線程池的好處有:
1.能夠設置最大線程池大小,支持線程池伸縮。
2.能夠使用有界隊列來存儲待執行的任務,避免任務過多致使內存溢出。
3.能夠設置任務拒絕策略,當線程池的任務緩存隊列已滿而且線程池中的線程數目達到maximumPoolSize時使用。
以下是ThreadPoolExecutor的構造參數定義:
序號 | 名稱 | 類型 | 含義 |
---|---|---|---|
1 | corePoolSize | int | 核心線程池大小 |
2 | maximumPoolSize | int | 最大線程池大小 |
3 | keepAliveTime | long | 線程最大空閒時間,時間單位在下一個參數定義 |
4 | unit | TimeUnit | 時間單位 |
5 | workQueue | BlockingQueue | 線程等待隊列 |
6 | threadFactory | ThreadFactory | 線程建立工廠 |
7 | handler | RejectedExecutionHandler | 拒絕策略 |
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/** * @ClassName ThreadPoolExecutorDemo * @Description * @Author 鏗然一葉 * @Date 2019/10/11 16:54 * @Version 1.0 * javashizhan.com **/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//任務隊列容量
final int QUEUE_CAPACITY = 20;
BlockingQueue<Runnable> runnableBlockedQueue = new LinkedBlockingDeque<Runnable>(QUEUE_CAPACITY);
//建立線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(4,10,60, TimeUnit.SECONDS, (BlockingQueue<Runnable>) runnableBlockedQueue);
int taskNo = 0;
int queueSize = 0;
while (true) {
//隨機生成任務數
int taskNum = getTaskNum();
for (int i = 0; i < taskNum; i++) {
taskNo++;
//隊列未滿則提交新任務
if (queueSize < QUEUE_CAPACITY) {
executor.execute(new Worker("Task" + taskNo));
} else {
//隊列滿了則等待一會重試, 注意:若是隊列滿了還提交任務會由於超出隊列容量而報錯。
while(true) {
sleep(200);
queueSize = executor.getQueue().size();
if (queueSize < QUEUE_CAPACITY) {
executor.execute(new Worker("Task" + taskNo));
break;
}
}
}
queueSize = executor.getQueue().size();
}
}
}
//隨機生成一次執行的任務數
private static int getTaskNum() {
return ((int)(1+Math.random()*(8-1+1)));
}
private static void sleep(long millis) {
try {
TimeUnit.MILLISECONDS.sleep(millis);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
class Worker implements Runnable {
private String name;
public Worker(String name) {
this.name = name;
}
public void run() {
exec();
}
//模擬任務執行
private void exec() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " be called.");
}
}
複製代碼
輸出日誌:
Task3 be called.
Task4 be called.
Task2 be called.
Task1 be called.
Task6 be called.
Task5 be called.
Task7 be called.
Task8 be called.
Task9 be called.
Task10 be called.
Task12 be called.
Task11 be called.
Task13 be called.
Task14 be called.
Task15 be called.
Task16 be called.
Task17 be called.
Task18 be called.
Task19 be called.
Task20 be called.
Task21 be called.
複製代碼
以上就是線程經常使用類和接口了。
end.
相關閱讀:
Java併發編程(一)知識地圖
Java併發編程(二)原子性
Java併發編程(三)可見性
Java併發編程(四)有序性
Java併發編程(五)建立線程方式概覽
Java併發編程入門(六)synchronized用法
Java併發編程入門(七)輕鬆理解wait和notify以及使用場景
Java併發編程入門(八)線程生命週期
Java併發編程入門(九)死鎖和死鎖定位
Java併發編程入門(十)鎖優化
Java併發編程入門(十一)限流場景和Spring限流器實現
Java併發編程入門(十二)生產者和消費者模式-代碼模板
Java併發編程入門(十三)讀寫鎖和緩存模板
Java併發編程入門(十四)CountDownLatch應用場景
Java併發編程入門(十五)CyclicBarrier應用場景
Java併發編程入門(十六)秒懂線程池差異
Java併發編程入門(十八)再論線程安全
Java極客站點: javageektour.com/