Java並發編程:Java實現多執行緒的幾種方式

  • 2019 年 10 月 7 日
  • 筆記

在Java中,多執行緒主要的實現方式有四種:繼承Thread類、實現Runnable介面、實現Callable介面通過FutureTask包裝器來創建Thread執行緒、使用ExecutorService、Callable、Future實現有返回結果的多執行緒。其中前兩種方式執行緒執行完後都沒有返回值,而後兩種是帶返回值的。除此之外,通過Timer啟動定時任務,或者通過像Spring Task和quartz這樣的第三方任務調度框架也可以開啟多執行緒任務。

1、繼承Thread類創建執行緒

Thread類本質上也是實現了Runnable介面的一個實例,代表一個執行緒的實例。啟動執行緒的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新執行緒,並執行run()方法。這種方式實現多執行緒比較簡單,通過繼承Thread類並複寫run()方法,就可以啟動新執行緒並執行自己定義的run()方法。

CreateThreadDemo1.java

public class CreateThreadDemo1 extends Thread {        public CreateThreadDemo1(String name) {          // 設置當前執行緒的名字          this.setName(name);      }        @Override      public void run() {          System.out.println("當前運行的執行緒名為: " + Thread.currentThread().getName());      }        public static void main(String[] args) throws Exception {          // 注意這裡,要調用start方法才能啟動執行緒,不能調用run方法          new CreateThreadDemo1("MyThread1").start();          new CreateThreadDemo1("MyThread2").start();        }    }

輸出結果:

當前運行的執行緒名為: MyThread1  當前運行的執行緒名為: MyThread2

2、實現Runnable介面創建執行緒 由於Java是單繼承機制,如果自己的類已經繼承自另一個類,則無法再直接繼承Thread類,此時,可以通過實現Runnable介面來實現多執行緒。

實現Runnable介面並實現其中的run方法,然後通過構造Thread實例,傳入Runnable實現類,然後調用Thread的start方法即可開啟一個新執行緒。

CreateThreadDemo2.java

public class CreateThreadDemo2 implements Runnable {        @Override      public void run() {          System.out.println("當前運行的執行緒名為: " + Thread.currentThread().getName());      }        public static void main(String[] args) throws Exception {          CreateThreadDemo2 runnable = new CreateThreadDemo2();          new Thread(runnable, "MyThread1").start();          new Thread(runnable, "MyThread2").start();        }    }

輸出結果:

當前運行的執行緒名為: MyThread1  當前運行的執行緒名為: MyThread2

3、實現Callable介面通過FutureTask包裝器來創建Thread執行緒

首先需要一個實現Callable介面的實例,然後實現該介面的唯一方法call邏輯,接著把Callable實例包裝成FutureTask傳遞給Thread實例啟動新執行緒。FutureTask本質上也實現了Runnable介面,所以同樣可以用來構造Thread實例。

CreateThreadDemo3.java

import java.util.concurrent.Callable;  import java.util.concurrent.FutureTask;    public class CreateThreadDemo3 {        public static void main(String[] args) throws Exception {          // 創建執行緒任務,lambada方式實現介面並實現call方法          Callable<Integer> callable = () -> {              System.out.println("執行緒任務開始執行了...");              Thread.sleep(2000);              return 1;          };            // 將任務封裝為FutureTask          FutureTask<Integer> task = new FutureTask<>(callable);            // 開啟執行緒,執行執行緒任務          new Thread(task).start();            // ====================          // 這裡是在執行緒啟動之後,執行緒結果返回之前          System.out.println("執行緒啟動之後,執行緒結果返回之前...");          // ====================            // 為所欲為完畢之後,拿到執行緒的執行結果          Integer result = task.get();          System.out.println("主執行緒中拿到非同步任務執行的結果為:" + result);        }    }

輸出結果:

執行緒啟動之後,執行緒結果返回之前...  執行緒任務開始執行了...  主執行緒中拿到非同步任務執行的結果為:1

4、使用ExecutorService、Callable、Future實現有返回結果的執行緒(執行緒池方式)

ExecutorService、Callable、Future三個介面都是屬於Executor框架。可返回值的任務必須實現Callable介面。通過ExecutorService執行Callable任務後,可以獲取到一個Future的對象,在該對象上調用get()就可以獲取到Callable任務返回的結果了。

注意:Future的get方法是阻塞的,即:執行緒無返回結果,get方法會一直等待。

CreateThreadDemo4.java

import java.util.ArrayList;  import java.util.Date;  import java.util.List;  import java.util.concurrent.Callable;  import java.util.concurrent.ExecutionException;  import java.util.concurrent.ExecutorService;  import java.util.concurrent.Executors;  import java.util.concurrent.Future;    public class CreateThreadDemo4 {        @SuppressWarnings({ "rawtypes", "unchecked" })      public static void main(String[] args) throws ExecutionException, InterruptedException {          System.out.println("---- 主程式開始運行 ----");          Date startTime = new Date();            int taskSize = 5;          // 創建一個執行緒池,Executors提供了創建各種類型執行緒池的方法,具體詳情請自行查閱          ExecutorService executorService = Executors.newFixedThreadPool(taskSize);            // 創建多個有返回值的任務          List<Future> futureList = new ArrayList<Future>();          for (int i = 0; i < taskSize; i++) {              Callable callable = new MyCallable(i);              // 執行任務並獲取Future對象              Future future = executorService.submit(callable);              futureList.add(future);          }            // 關閉執行緒池          executorService.shutdown();            // 獲取所有並發任務的運行結果          for (Future future : futureList) {              // 從Future對象上獲取任務的返回值,並輸出到控制台              System.out.println(">>> " + future.get().toString());          }            Date endTime = new Date();          System.out.println("---- 主程式結束運行 ----,程式運行耗時【" + (endTime.getTime() - startTime.getTime()) + "毫秒】");      }  }    class MyCallable implements Callable<Object> {      private int taskNum;        MyCallable(int taskNum) {          this.taskNum = taskNum;      }        public Object call() throws Exception {          System.out.println(">>> " + taskNum + " 執行緒任務啟動");          Date startTime = new Date();          Thread.sleep(1000);          Date endTime = new Date();          long time = endTime.getTime() - startTime.getTime();          System.out.println(">>> " + taskNum + " 執行緒任務終止");          return taskNum + "執行緒任務返回運行結果, 當前任務耗時【" + time + "毫秒】";      }  }

輸出結果:

---- 主程式開始運行 ----  >>> 0 執行緒任務啟動  >>> 1 執行緒任務啟動  >>> 2 執行緒任務啟動  >>> 3 執行緒任務啟動  >>> 4 執行緒任務啟動  >>> 0 執行緒任務終止  >>> 1 執行緒任務終止  >>> 0執行緒任務返回運行結果, 當前任務耗時【1001毫秒】  >>> 1執行緒任務返回運行結果, 當前任務耗時【1001毫秒】  >>> 4 執行緒任務終止  >>> 3 執行緒任務終止  >>> 2 執行緒任務終止  >>> 2執行緒任務返回運行結果, 當前任務耗時【1001毫秒】  >>> 3執行緒任務返回運行結果, 當前任務耗時【1001毫秒】  >>> 4執行緒任務返回運行結果, 當前任務耗時【1001毫秒】  ---- 主程式結束運行 ----,程式運行耗時【1009毫秒】

5、其他創建執行緒的方式

當然,除了以上四種主要的執行緒創建方式之外,也還有很多其他的方式可以啟動多執行緒任務。比如通過Timer啟動定時任務,或者通過像Spring Task和quartz這樣的第三方任務調度框架也可以開啟多執行緒任務,關於第三方任務調度框架的例子還請查詢相關資料。

源碼下載

碼云:https://gitee.com/liuge1988/java-demo.git


作者:朝雨憶輕塵 出處:https://www.cnblogs.com/xifengxiaoma/  版權所有,歡迎轉載,轉載請註明原文作者及出處。