談談多線程有幾種獲取方式,你面試肯定被問過!!

獲取多線程的多種方式

Java獲取多線程的方式總共有四種,在Jdk5之前只有兩種,Jdk5之後新增到了四種。

  1. 繼承Thread類
  2. 實現Runnable接口
  3. 實現Callable接口
  4. 從線程池中獲取

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方法中調用了對象本身的構造函數,我們再來看看。

  1. corePoolSize:線程池中的常駐核心線程數
  2. maximumPoolSize:線程池中能容納同時執行的最大線程數,必須大於等於1
  3. keepAliveTime:多餘的空閒線程存活時間
  4. unit:keepAliveTime的時間單位
  5. workQueue:提交但未被執行的任務隊列(阻塞隊列)
  6. threadFactory:線程池中生成工作線程的線程工廠
  7. handler:拒絕策略
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章