作者:唐彤
簡介
創建線程,是多線程編程中最基本的操作,彤哥總結了一下,大概有8種創建線程的方式,你知道嗎?
1.繼承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()方法,這種方式的弊端是一個類只能繼承一個父類,如果這個類本身已經繼承了其它類,就不能使用這種方式了。
2.實現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接口,這種方式的好處是一個類可以實現多個接口,不影響其繼承體系。
3.匿名內部類
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+),簡單快捷。
4.實現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接口。
5.定時器(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接口。
6.線程池
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")); } } }
使用線程池的方式,可以復用線程,節約系統資源。
7.并行計算(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); } }
使用并行計算的方式,可以提高程序運行的效率,多線程并行執行。
8.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()方法中的內容。
最后
歡迎大家一起交流,喜歡文章記得關注我點贊轉發喲,感謝支持!