死磕 java線程系列之創建線程的8種方式


(手機橫屏看源碼更方便)


問題

(1)創建線程有哪幾種方式?

(2)它們分別有什么運用場景?

簡介

創建線程,是多線程編程中最基本的操作,彤哥總結了一下,大概有8種創建線程的方式,你知道嗎?

繼承Thread類並重寫run()方法

public class CreatingThread01 extends Thread { @Override public void run() { System.out.println(getName() + " is running"); } public static void main(String[] args) { new CreatingThread01().start(); new CreatingThread01().start(); new CreatingThread01().start(); new CreatingThread01().start(); } }

繼承Thread類並重寫run()方法,這種方式的弊端是一個類只能繼承一個父類,如果這個類本身已經繼承了其它類,就不能使用這種方式了。

實現Runnable接口

public class CreatingThread02 implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } public static void main(String[] args) { new Thread(new CreatingThread02()).start(); new Thread(new CreatingThread02()).start(); new Thread(new CreatingThread02()).start(); new Thread(new CreatingThread02()).start(); } }

實現Runnable接口,這種方式的好處是一個類可以實現多個接口,不影響其繼承體系。

匿名內部類

public class CreatingThread03 { public static void main(String[] args) { // Thread匿名類,重寫Thread的run()方法 new Thread() { @Override public void run() { System.out.println(getName() + " is running"); } }.start(); // Runnable匿名類,實現其run()方法 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } }).start(); // 同上,使用lambda表達式函數式編程 new Thread(()->{ System.out.println(Thread.currentThread().getName() + " is running"); }).start(); } }

使用匿名類的方式,一是重寫Thread的run()方法,二是傳入Runnable的匿名類,三是使用lambda方式,現在一般使用第三種(java8+),簡單快捷。

實現Callabe接口

public class CreatingThread04 implements Callable<Long> { @Override public Long call() throws Exception { Thread.sleep(2000); System.out.println(Thread.currentThread().getId() + " is running"); return Thread.currentThread().getId(); } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Long> task = new FutureTask<>(new CreatingThread04()); new Thread(task).start(); System.out.println("等待完成任務"); Long result = task.get(); System.out.println("任務結果:" + result); } }

實現Callabe接口,可以獲取線程執行的結果,FutureTask實際上實現了Runnable接口。

定時器(java.util.Timer)

public class CreatingThread05 { public static void main(String[] args) { Timer timer = new Timer(); // 每隔1秒執行一次 timer.schedule(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } }, 0 , 1000); } }

使用定時器java.util.Timer可以快速地實現定時任務,TimerTask實際上實現了Runnable接口。

線程池

public class CreatingThread06 { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 100; i++) { threadPool.execute(()-> System.out.println(Thread.currentThread().getName() + " is running")); } } }

使用線程池的方式,可以復用線程,節約系統資源。

並行計算(Java8+)

public class CreatingThread07 { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); // 串行,打印結果為12345 list.stream().forEach(System.out::print); System.out.println(); // 並行,打印結果隨機,比如35214 list.parallelStream().forEach(System.out::print); } }

使用並行計算的方式,可以提高程序運行的效率,多線程並行執行。

Spring異步方法

首先,springboot啟動類加上@EnableAsync注解(@EnableAsync是spring支持的,這里方便舉例使用springboot)。

@SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

其次,方法加上@Async注解。

@Service public class CreatingThread08Service { @Async public void call() { System.out.println(Thread.currentThread().getName() + " is running"); } }

然后,測試用例直接跟使用一般的Service方法一模一樣。

@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class CreatingThread08Test { @Autowired private CreatingThread08Service creatingThread08Service; @Test public void test() { creatingThread08Service.call(); creatingThread08Service.call(); creatingThread08Service.call(); creatingThread08Service.call(); } }

運行結果如下:

task-3 is running task-2 is running task-1 is running task-4 is running

可以看到每次執行方法時使用的線程都不一樣。

使用Spring異步方法的方式,可以說是相當地方便,適用於前后邏輯不相關聯的適合用異步調用的一些方法,比如發送短信的功能。

總結

(1)繼承Thread類並重寫run()方法;

(2)實現Runnable接口;

(3)匿名內部類;

(4)實現Callabe接口;

(5)定時器(java.util.Timer);

(6)線程池;

(7)並行計算(Java8+);

(8)Spring異步方法;

彩蛋

上面介紹了那么多創建線程的方式,其實本質上就兩種,一種是繼承Thread類並重寫其run()方法,一種是實現Runnable接口的run()方法,那么它們之間到底有什么聯系呢?

請看下面的例子,同時繼承Thread並實現Runnable接口,應該輸出什么呢?

public class CreatingThread09 { public static void main(String[] args) { new Thread(()-> { System.out.println("Runnable: " + Thread.currentThread().getName()); }) { @Override public void run() { System.out.println("Thread: " + getName()); } }.start(); } }

說到這里,我們有必要看一下Thread類的源碼:

public class Thread implements Runnable { // Thread維護了一個Runnable的實例 private Runnable target; public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // ... // 構造方法傳進來的Runnable會賦值給target this.target = target; // ... } @Override public void run() { // Thread默認的run()方法,如果target不為空,會執行target的run()方法 if (target != null) { target.run(); } } }

看到這里是不是豁然開朗呢?既然上面的例子同時繼承Thread並實現了Runnable接口,根據源碼,實際上相當於重寫了Thread的run()方法,在Thread的run()方法時實際上跟target都沒有關系了。

所以,上面的例子輸出結果為Thread: Thread-0,只輸出重寫Thread的run()方法中的內容。


歡迎關注我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

qrcode

歡迎關注我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM