
# java創建并運行線程的五種方法區別
[TOC]
## 一、前言
熟悉我的朋友可能都知道,我寫的文章偏于應用實戰,絕大多數是為了解決實際生產中遇到的問題。

上面的這個問題是我的一位小伙伴向我提出的問題,當時由于工作太忙并沒有回復他,那就通過這篇文章回復了。解決應用高并發的問題不是一兩句話能說清楚的,也**不是一兩本書能講明白的**,這里面涉及到知識點比較很多很多,比如:并發編程、應用緩存設計、消息中間件、負載均衡、微服務架構、docker&k8s服務擴容縮容等等,都是為了應對“三高”:高并發、高性能、高可用。
這其中并發編程就是基礎中的基礎,我的CSDN專欄《并發編程》中已經寫了20篇關于java并發編程方面的文章,但我感覺這還僅僅是其中的冰山一角。這個專欄我還會繼續寫下去,這一篇的內容相對基礎:**創建線程的5種方式**。
## 二、創建并運行線程的四種方法
### 第一種:繼承Thread類
這種方式是最基礎的一種方式,學過java的朋友都知道,不做贅述。需要注意的是:**覆蓋實現使用的是run方法,運行線程是start方法。**
~~~
public class FirstWay extends Thread {
@Override
public void run() {
System.out.println("第一種實現線程的方式:繼承Thread類");
}
//模擬測試
public static void main(String[] args) {
new FirstWay().start();
}
}
~~~
### 第二種:實現Runnable接口
第二種實現方式仍然很基礎,繼承Runnable接口,重寫run方法實現線程運行邏輯。需要注意的:運行線程需要套一層`new Thread` 。
~~~
public class SecondWay implements Runnable{
@Override
public void run() {
System.out.println("第二種實現線程的方式:實現Runnable接口");
}
//模擬測試
public static void main(String[] args) {
new Thread(new SecondWay()).start();
}
}
~~~
### 第三種:實現Callable接口
第三種方式是實現Callable接口,Callable接口與Runable接口都能實現線程。
~~~
public class ThirdWay implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("第三種實現線程的方式:實現Callable接口");
return "Callable接口帶返回值,可以拋出異常";
}
//模擬測試
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new ThirdWay());
new Thread(futureTask).start();
//阻塞方法,獲取call方法返回值
System.out.println(futureTask.get()); //打印:Callable接口帶返回值,可以拋出異常
}
}
~~~
區別如下:
* Callable接口實現線程方法是call, Runable接口實現線程方法是run
* Callable有返回值, Runable接口不能有返回值
* Callable接口方法call返回值可以設置泛型,如下例子中使用String數據類型
* Callable接口方法call方法可以拋出異常,Runable接口run方法不可以拋出異常
* Callable接口方法通過`new Thread(futureTask).start()`運行,FutureTask的get方法可以獲取Callable接口方法call方法的返回值
* 如果Callable接口方法call方法異常,在FutureTask的get方法調用時也會拋出同樣的異常
### 第四種:線程池 + execute
從JDK5版本開始,java默認提供了線程池的支持,用線程池的方式運行線程可以避免線程的無限擴張導致應用宕機,同時也節省了線程頻繁創建與銷毀的資源與時間成本。
~~~
public class FourthWay implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
":實現線程的方式Runnable接口,但運行方式不一樣,使用線程池");
}
public static void main(String[] args) {
//創建一個固定大小的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for(int i = 0;i < 10;i++){
threadPool.execute(new FourthWay());
}
}
}
~~~
**線程池ExecutorService使用execute方法運行Runnable接口run方法的線程實現,execute方法與run方法的共同特點是沒有返回值。**
~~~
pool-1-thread-5:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-2:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-4:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-4:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-4:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-1:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-4:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-3:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-2:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
pool-1-thread-5:實現線程的方式Runnable接口,但運行方式不一樣,使用線程池
~~~
從上面的結果中可以看出,線程池中包含五個線程。線程運行完成之后并不銷毀,而是還回到線程池,下一次執行時從線程池中獲取線程資源再次運行。
### 第五種:線程池 + submit
**下面的例子線程池ExecutorService使用submit方法運行Callable接口call方法的線程實現,submit方法與call方法的共同特點是存在返回值。**
* Callable接口call方法的返回值可以由泛型定義
* ExecutorService線程池submit方法的返回值是Future
Future的get方法可以獲取call方法的返回值,同時**如果call方法拋出異常,Future的get方法也會拋出異常。**
~~~
public class FifthWay implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + ":Callable接口帶返回值,可以拋出異常";
}
//模擬測試
public static void main(String[] args) throws ExecutionException, InterruptedException {
//保存多線程執行結果
List<String> retList = new ArrayList<>();
//創建一個固定大小的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for(int i = 0;i < 10;i++){
Future<String> future = threadPool.submit(new FifthWay());
retList.add(future.get());
}
//java8 語法,打印retlist
retList.forEach(System.out::println);
}
}
~~~
上文代碼中有一個小小的語法糖,`retList.forEach(System.out::println);`是java8提供的方法引用,我也寫過很多關于java8的一些有趣的用法,可以看我的CSDN博客專欄。
~~~
pool-1-thread-1:Callable接口帶返回值,可以拋出異常
pool-1-thread-2:Callable接口帶返回值,可以拋出異常
pool-1-thread-3:Callable接口帶返回值,可以拋出異常
pool-1-thread-4:Callable接口帶返回值,可以拋出異常
pool-1-thread-5:Callable接口帶返回值,可以拋出異常
pool-1-thread-1:Callable接口帶返回值,可以拋出異常
pool-1-thread-2:Callable接口帶返回值,可以拋出異常
pool-1-thread-3:Callable接口帶返回值,可以拋出異常
pool-1-thread-4:Callable接口帶返回值,可以拋出異常
pool-1-thread-5:Callable接口帶返回值,可以拋出異常
~~~
- 線程
- 1.進程和線程-鎖與信號量
- 2.Thread類線程狀態轉換
- 2.并發與并行-同步與異步
- 4.線程池
- 5.對象級別與類級別的同步鎖
- 6.創建線程的四種方式
- 7.臨界區-阻塞-活鎖-死鎖
- 2.JMM多線程模型
- JUC
- BlockingQueue
- ArrayBlockingQueue
- DelayQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- SynchronousQueue
- BlockingDeque
- ConcurrentHashMap
- CountDownLatch
- CyclicBarrier
- Exchanger
- AtomicInteger
- Lock
- Condition
- ReentrantLock讀寫鎖
- StampedLock
- Semaphore