獲取多線程的多種方式
Java獲取多線程的方式總共有四種,在Jdk5之前只有兩種,Jdk5之後新增到了四種。
- 繼承Thread類
- 實現Runnable接口
- 實現Callable接口
- 從線程池中獲取
1.繼承Thread類
資源類繼承Thread類,重寫父類的run方法。
class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
thread.start();
}
2.實現Runnable接口
資源類實現Runnable接口,實現接口中的run方法。
class MyThread1 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyThread1());
thread.start();
}
3.實現Callable接口
通過Jdk可以查看Thread類的構造方法,發現根本沒有入參是Callable類的,那麼需要如何才能使用呢?
我們可以通過Java多態的思想,找到一個類既能關聯Callable又能關聯Runnable。
可以發現Runnable接口下有一個FutureTask實現類。
FutureTask的構造方法中就包含了我們需要使用到的Callable接口。
FutureTask在線程啓動時,會創建一個新的線程與主分支線程分開,在底層進行call方法裏的操作得到結果,在主線程中
獲取結果可以通過
futureTask.get()
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 1024;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//創建FutureTask對象
FutureTask futureTask = new FutureTask<>(new MyThread2());
//創建線程
new Thread(futureTask, "A").start();
//獲取計算結果
System.out.println(futureTask.get());
}
那麼使用FutureTask相比於實現Runnable接口有什麼區別呢?
衆所周知,在多線程環境中,最不希望就是見到線程阻塞。
Runnable
在實現Runnable接口的情況下,如果多個線程中有一個線程執行時間過長時,整個程序就會阻塞住,只有等到線程執行完才能繼續執行。
FutureTask
在使用FutureTask的情況下,如果多個線程中有一個線程執行時間過長時,FutureTask底層會啓動一個新線程去執行,與主線程並行執行,等到執行完畢後,從新線程中得到執行結果與主線程結果合併即可。
4.從線程池中獲取
線程池是一種池化技術,在以前的學習中,肯定也瞭解過c3p0與dbcp,這兩個是數據庫連接池,也是一種池化技術。
原理都類似,將事前創建好的多個線程放入連接池中,每次有線程訪問都通過線程池獲取線程,使用完畢後歸還,避免了創建和關閉線程的開銷。
在Jdk的api文檔中,Executor接口是線程池最頂級的父級接口,其定義了一個接收Runnable對象的方法executor。
其下還有ExecutorService子接口,一般使用的是ExecutorService接口,因爲子接口的功能比父接口多,其提供了生命週期管理的方法,返回Future對象,以及可跟蹤一個或多個異步任務執行狀況返回Future的方法
有接口就肯定有實現類,Jdk中提供了Executors類,其本質就是new了一個ThreadPoolExecutor對象。
常用的構造方法
static ExecutorService newFixedThreadPool(int nThreads)
創建一個線程池,該線程池重用固定數量的從共享無界隊列中運行的線程。
static ExecutorService newSingleThreadExecutor()
創建一個使用從無界隊列運行的單個工作線程的執行程序。
static ExecutorService newCachedThreadPool()
創建一個根據需要創建新線程的線程池,但在可用時將重新使用以前構造的線程(可擴容)。
但是阿里巴巴開發手冊中提到:【強制】線程池不允許使用 Executors 去創建。
Executors 返回的線程池對象的弊端如下:
FixedThreadPool、SingleThreadExecutor:允許的請求阻塞隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM;
我們來看看該方法的源碼,可以看出方法調用了ThreadPoolExecutor方法。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看出他們都有一個相同的LinkedBlockingQueue隊列,而這個阻塞隊列就是罪魁禍首,默認長度爲Integer.MAX_VALUE。
new LinkedBlockingQueue()
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
而CachedThreadPool由於是可伸縮的線程池,其長度是不固定的,所以在初始化的時候使用Integer.MAX_VALUE,並不好。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
線程池的7大參數
ThreadPoolExecutor方法
在ThreadPoolExecutor方法中調用了對象本身的構造函數,我們再來看看。
- corePoolSize:線程池中的常駐核心線程數
- maximumPoolSize:線程池中能容納同時執行的最大線程數,必須大於等於1
- keepAliveTime:多餘的空閒線程存活時間
- unit:keepAliveTime的時間單位
- workQueue:提交但未被執行的任務隊列(阻塞隊列)
- threadFactory:線程池中生成工作線程的線程工廠
- handler:拒絕策略