創建執行緒的幾種方式
1?? 繼承 Thread 類
繼承 Thread 類創建執行緒的步驟為:
1)創建一個類繼承Thread類,重寫run()方法,將所要完成的任務代碼寫進run()方法中;
2)創建Thread類的子類的物件;
3)呼叫該物件的start()方法,該start()方法表示先開啟執行緒,然后呼叫run()方法;
@Slf4j
public class ExtendsThread {
static class T extends Thread {
@Override
public void run() {
log.debug("hello");
}
}
public static void main(String[] args) {
T t = new T();
t.setName("t1");
t.start();
}
}
也可以直接使用Thread 類創建執行緒:
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
log.debug("hello");
}
}, "t1");
}
看看 Thread 類的構造器,Thread 類有多個構造器來創建需要的執行緒物件:
// Thread.java
public Thread() {}
public Thread(Runnable target) {}
public Thread(String name) {}
public Thread(Runnable target, String name) {}
// 還有幾個使用執行緒組創建執行緒的構造器,就不列舉了
2?? Runnable 介面配合 Thread
實作 Runnable 介面創建執行緒的步驟為:
1)創建一個類并實作 Runnable 介面;
2)重寫 run() 方法,將所要完成的任務代碼寫進 run() 方法中;
3)創建實作 Runnable 介面的類的物件,將該物件當做 Thread 類的構造方法中的引數傳進去;
4)使用 Thread 類的構造方法創建一個物件,并呼叫 start() 方法即可運行該執行緒;
@Slf4j
public class ImplRunnable {
static class T implements Runnable {
@Override
public void run() {
log.debug("hello");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new T(), "t1");
t1.start();
}
}
也可以寫成這樣:
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
Thread t2 = new Thread(task, "t2");
t2.start();
}
Java 8 以后可以使用 lambda 精簡代碼(IDEA會有提示可將匿名內部類換成 Lambda 運算式):
public static void main(String[] args) {
Runnable task = () -> log.debug("hello");
Thread t2 = new Thread(task, "t2");
t2.start();
}
查看一下 Runnable 介面的原始碼,可以看到 Runnable 介面中只有一個抽象方法 run(),這種只有一個抽象方法的介面會加上一個注解:@FunctionalInterface,那只要帶有這個注解的介面就可以被Lambda運算式來簡化,
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
分析一下原始碼:
public static void main(String[] args) {
Runnable task = () -> log.debug("hello");
Thread t2 = new Thread(task, "t2");
t2.start();
}
??;
public Thread(Runnable target, String name) { // 呼叫Thread的指定構造器
init(null, target, name, 0);
}
??;
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
init(g, target, name, stackSize, null, true);
}
??;
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
....
this.target = target; // target是Thread類中的成員變數,private Runnable target
....
}
??;
@Override
public void run() {
if (target != null) {
target.run();
}
}
Thread類是實作了Runnable介面的,那Thread類中重寫了Runnable介面的run()方法,在創建Thread類的物件時,如果傳進來的形參target不為空,那他就會去執行target的run(),也就是我們創建實作Runnable的實作類時重寫的run()方法,
3?? FutureTask 配合 Thread
這種通過實作 Callable 介面來實作的,步驟為:
1)創建一個類并實作 Callable 介面;
2)重寫call()方法,將所要完成的任務的代碼寫進call()方法中,需要注意的是call()方法有回傳值,并且可以拋出例外;
3)如果想要獲取運行該執行緒后的回傳值,需要創建 Future 介面的實作類的物件,即 FutureTask 類的物件,呼叫該物件的 get() 方法可獲取call() 方法的回傳值;
4)使用 Thread 類的有參構造器創建物件,將 FutureTask 類的物件當做引數傳進去,然后呼叫 start() 方法開啟并運行該執行緒;
@Slf4j
public class ImplCallable {
static class MT implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + " finished!";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 創建 FutureTask 的物件
FutureTask<String> task = new FutureTask<>(new MT());
// 創建 Thread 類的物件
Thread t1 = new Thread(task, "t1");
t1.start();
// 獲取 call() 方法的回傳值
String ret = task.get();
log.debug(ret);
}
}
// --------------運行結果:
00:33:29.628 [main] DEBUG cn.peng.create.ImplCallable - t1 finished!
FutureTask 能夠接收 Callable 型別的引數,用來處理執行緒執行結束,有回傳結果的情況
上面代碼同樣可以使用 Lambda 運算式簡寫:
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 匿名內部類方式
/*
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.debug("call() running....");
return 5 + 3;
}
});
*/
// Lambda 簡寫
FutureTask<Integer> task = new FutureTask<>(() -> {
log.debug("call() running....");
return 5 + 3;
});
new Thread(task, "t2").start();
Integer ret = task.get();
log.debug("ret: {}", ret);
}
// -------運行結果
00:41:45.300 [main] DEBUG cn.peng.create.ImplCallable - ret: 8
看下原始碼,Callable 介面中也只有一個抽象方法 call(),并且call()方法支持回傳值,含有注解:@FunctionalInterface,可以被Lambda運算式簡化,
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
FutureTask 類實作了 RunnableFuture介面,而 RunnableFuture介面又同時多繼承了 Runnable, Future<V>介面,因此可以作為Thread 類的target,
public class FutureTask<V> implements RunnableFuture<V> {...}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
三種方式對比
采用實作Runnable、Callable介面的方式創建執行緒時,
- 執行緒類只是實作了Runnable介面或Callable介面,還可以繼承其他類;
- 在這種方式下,多個執行緒可以共享同一個target物件,非常適合多個相同執行緒來處理同一份資源的情況;
- 實作 Callable 介面執行 call() 方法有回傳值,而實作Runnable 介面執行 run() 方法無回傳值;
- 編程稍微復雜,如果要訪問當前執行緒,則必須使用
Thread.currentThread()方法;
而使用繼承 Thread 類的方式創建多執行緒時,
- 執行緒類已經繼承了Thread類,所以不能再繼承其他父類(Java單繼承);
- 撰寫簡單,如果需要訪問當前執行緒,則無需使用
Thread.currentThread()方法,直接使用this即可獲得當前執行緒;
4?? 執行緒池
查看下ThreadPoolExecutor類中引數最全的構造方法,共有七大引數;
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
corePoolSize 核心執行緒數目 (最多保留的執行緒數)
maximumPoolSize 最大執行緒數目
keepAliveTime 生存時間 - 針對救急執行緒
unit 時間單位 - 針對救急執行緒
workQueue 阻塞佇列
threadFactory 執行緒工廠 - 可以為執行緒創建時起個好名字
handler 拒絕策略
根據這個構造方法,JDK Executors 類中提供了眾多工廠方法來創建各種用途的執行緒池,
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 這種通過 ThreadFactory 指定執行緒名稱
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
創建一個固定大小的執行緒池,通過
Executors.newFixedThreadPool(執行緒個數)即可創建,執行緒池特點:
- 核心執行緒數 == 最大執行緒數(沒有救急執行緒被創建),因此也無需超時時間,時間被設定成了0;
- 阻塞佇列
LinkedBlockingQueue是無界的,可以放任意數量的任務;適用場景:
? 這種執行緒池適用于任務量已知,相對耗時的任務;
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 這種通過 ThreadFactory 指定執行緒名稱
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
創建一個帶緩沖功能的執行緒池,通過
Executors.newCachedThreadPool(執行緒個數)即可創建,執行緒池特點:
- 核心執行緒為0,救急執行緒為 int 最大值,該執行緒池創建出的執行緒全是救急執行緒;
- 每個救急執行緒空閑存活時間 60s;
- 佇列采用了
SynchronousQueue,實作特點是,它沒有容量,沒有執行緒來取是放不進去的(一手交錢、一手交貨);適用場景:
? 執行緒池表現為執行緒數會根據任務量不斷增長,沒有上限;當任務執行完畢,空閑 1分鐘后釋放執行緒,適合任務數比較密集,但每個任務執行時間較短的情況,
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
創建一個單執行緒的執行緒池,通過
Executors.newSingleThreadExecutor()即可創建,執行緒池特點:
- 只有1個核心執行緒,沒有救急執行緒,所以救急執行緒空閑存活時間為0;
- 阻塞佇列
LinkedBlockingQueue是無界的,可以放任意數量的任務;- 任務執行完畢,這唯一的執行緒也不會被釋放,
適用場景:
? 希望多個任務排隊串行執行,(執行緒數固定為 1,任務數多于 1 時,會放入無界佇列排隊)
看看面試題
Q:單執行緒的執行緒池和自己創建一個執行緒執行任務上的區別?
A:自己創建一個單執行緒(如實作Runnable介面)串行執行任務,如果任務執行失敗導致執行緒終止,那么沒有任何補救措施;而單執行緒的執行緒池
newSingleThreadExecutor出現了這種情況時,還會新建一個執行緒,保證執行緒池的正常作業,
舉個例子:
@Slf4j
public class TestExecutors {
public static void main(String[] args) {
test();
}
private static void test() {
ExecutorService pool = Executors.newSingleThreadExecutor();
// 任務1
pool.execute(() -> {
log.debug("1");
int i = 1 / 0; // 手動寫例外
});
// 任務2
pool.execute(() -> {
log.debug("2");
});
// 任務3
pool.execute(() -> {
log.debug("3");
});
}
}
//-----運行結果:任務1中出現例外,但任務2和任務3都執行完了,任務執行完畢,這唯一的執行緒也不會被釋放,
13:47:26.448 [pool-1-thread-1] DEBUG cn.peng.threadpool.TestExecutors - 1
13:47:26.453 [pool-1-thread-2] DEBUG cn.peng.threadpool.TestExecutors - 2
13:47:26.453 [pool-1-thread-2] DEBUG cn.peng.threadpool.TestExecutors - 3
Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
at cn.peng.threadpool.TestExecutors.lambda$test$0(TestExecutors.java:25)
Q:單執行緒的執行緒池
newSingleThreadExecutor和創建固定數量為1的執行緒池newFixedThreadPool(1)有什么區別?A:看兩者的構造方法,
newFixedThreadPool回傳的是 ThreadPoolExecutor 物件,可以強轉后呼叫 setCorePoolSize 等方法進行修改執行緒池的配置;- 而
newSingleThreadExecutor在 ThreadPoolExecutor 的外層做了一個包裝,FinalizableDelegatedExecutorService應用的是裝飾器模式,只對外暴露了 ExecutorService 介面,因此不能呼叫 ThreadPoolExecutor 中特有的方法,即不能修改執行緒池的配置,
來看看 Executor、Executors、ExecutorService、ThreadPoolExecutor這幾個有什么聯系,
-
Executor是一個介面,內部只有一個抽象方法execute();void execute(Runnable command); -
ExecutorService也是一個介面,繼承自Executor,定義了一些操作執行緒池的抽象方法:void shutdown(); // 執行任務 List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); <T> Future<T> submit(Callable<T> task); ...... -
Executors是一個工具類,內部提供了眾多工廠方法來創建各種用途的執行緒池; -
ThreadPoolExecutor是一個類,對于不同的業務可以自定義不同的執行緒池,上面三個官方自帶的執行緒池都是利用ThreadPoolExecutor來實作所需要的功能池,

newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

創建帶有任務調度功能的執行緒池,執行緒池支持定時以及周期性執行任務,ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor,間接實作了 ExecutorService介面,在ExecutorService的基礎上新增了一些方法,如 schedule()、scheduleAtFixedRate() 等,
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor
implements ScheduledExecutorService {
// 構造方法
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {}
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {}
// scheduleAtFixedRate(任務物件,延時時間,執行間隔,時間單位)
// 表示執行緒第一次執行時,過了延時時間后再去處理引數一中的任務
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {}
通過
Executors.newScheduledThreadPool(核心執行緒數)來創建任務調度執行緒池;適用場景:
- 整個執行緒池表現為:執行緒數固定,任務數多于執行緒數時,會放入無界佇列排隊;
任務執行完畢,執行緒池中的執行緒也不會被釋放,用來執行延遲或反復執行的任務,(只有核心執行緒、沒有救急執行緒)
總結
前三種方法創建關閉頻繁會消耗系統資源影響性能,而使用執行緒池可以不用執行緒的時候放回執行緒池,用的時候再從執行緒池取,專案開發中主要使用執行緒池,
前三種一般推薦采用實作介面的方式來創建執行緒,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/502620.html
標籤:其他
上一篇:MyBatis(三)-動態SQL
下一篇:一、物件與類
