死磕 Java線程系列之創(chuàng)建線程的8種方式
簡介
創(chuàng)建線程,是多線程編程中最基本的操作,彤哥總結(jié)了一下,大概有8種創(chuàng)建線程的方式,你知道嗎?
繼承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()方法,這種方式的弊端是一個(gè)類只能繼承一個(gè)父類,如果這個(gè)類本身已經(jīng)繼承了其它類,就不能使用這種方式了。
實(shí)現(xiàn)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();
- }
- }
實(shí)現(xiàn)Runnable接口,這種方式的好處是一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,不影響其繼承體系。
匿名內(nèi)部類
- 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匿名類,實(shí)現(xiàn)其run()方法
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + " is running");
- }
- }).start();
- // 同上,使用lambda表達(dá)式函數(shù)式編程
- new Thread(()->{
- System.out.println(Thread.currentThread().getName() + " is running");
- }).start();
- }
- }
使用匿名類的方式,一是重寫Thread的run()方法,二是傳入Runnable的匿名類,三是使用lambda方式,現(xiàn)在一般使用第三種(java8+),簡單快捷。
實(shí)現(xiàn)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("等待完成任務(wù)");
- Long result = task.get();
- System.out.println("任務(wù)結(jié)果:" + result);
- }
- }
實(shí)現(xiàn)Callabe接口,可以獲取線程執(zhí)行的結(jié)果,F(xiàn)utureTask實(shí)際上實(shí)現(xiàn)了Runnable接口。
定時(shí)器(java.util.Timer)
- public class CreatingThread05 {
- public static void main(String[] args) {
- Timer timer = new Timer();
- // 每隔1秒執(zhí)行一次
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + " is running");
- }
- }, 0 , 1000);
- }
- }
使用定時(shí)器java.util.Timer可以快速地實(shí)現(xiàn)定時(shí)任務(wù),TimerTask實(shí)際上實(shí)現(xiàn)了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"));
- }
- }
- }
使用線程池的方式,可以復(fù)用線程,節(jié)約系統(tǒng)資源。
并行計(jì)算(Java8+)
- public class CreatingThread07 {
- public static void main(String[] args) {
- List<integer> list = Arrays.asList(1, 2, 3, 4, 5);
- // 串行,打印結(jié)果為12345
- list.stream().forEach(System.out::print);
- System.out.println();
- // 并行,打印結(jié)果隨機(jī),比如35214
- list.parallelStream().forEach(System.out::print);
- }
- }
使用并行計(jì)算的方式,可以提高程序運(yùn)行的效率,多線程并行執(zhí)行。
Spring異步方法
首先,springboot啟動(dòng)類加上@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();
- }
- }
運(yùn)行結(jié)果如下:
- task-3 is running
- task-2 is running
- task-1 is running
- task-4 is running
可以看到每次執(zhí)行方法時(shí)使用的線程都不一樣。
使用Spring異步方法的方式,可以說是相當(dāng)?shù)胤奖?,適用于前后邏輯不相關(guān)聯(lián)的適合用異步調(diào)用的一些方法,比如發(fā)送短信的功能。
總結(jié)
(1)繼承Thread類并重寫run()方法;
(2)實(shí)現(xiàn)Runnable接口;
(3)匿名內(nèi)部類;
(4)實(shí)現(xiàn)Callabe接口;
(5)定時(shí)器(java.util.Timer);
(6)線程池;
(7)并行計(jì)算(Java8+);
(8)Spring異步方法;
彩蛋
上面介紹了那么多創(chuàng)建線程的方式,其實(shí)本質(zhì)上就兩種,一種是繼承Thread類并重寫其run()方法,一種是實(shí)現(xiàn)Runnable接口的run()方法,那么它們之間到底有什么聯(lián)系呢?
請看下面的例子,同時(shí)繼承Thread并實(shí)現(xiàn)Runnable接口,應(yīng)該輸出什么呢?
- 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維護(hù)了一個(gè)Runnable的實(shí)例
- 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) {
- // ...
- // 構(gòu)造方法傳進(jìn)來的Runnable會(huì)賦值給target
- this.target = target;
- // ...
- }
- @Override
- public void run() {
- // Thread默認(rèn)的run()方法,如果target不為空,會(huì)執(zhí)行target的run()方法
- if (target != null) {
- target.run();
- }
- }
- }
看到這里是不是豁然開朗呢?既然上面的例子同時(shí)繼承Thread并實(shí)現(xiàn)了Runnable接口,根據(jù)源碼,實(shí)際上相當(dāng)于重寫了Thread的run()方法,在Thread的run()方法時(shí)實(shí)際上跟target都沒有關(guān)系了。
所以,上面的例子輸出結(jié)果為Thread: Thread-0,只輸出重寫Thread的run()方法中的內(nèi)容。