Java併發編程:Java實現多線程的幾種方式

在Java中,多線程主要的實現方式有四種:繼承Thread類、實現Runnable接口、實現Callable接口經過FutureTask包裝器來建立Thread線程、使用ExecutorService、Callable、Future實現有返回結果的多線程。其中前兩種方式線程執行完後都沒有返回值,然後兩種是帶返回值的。除此以外,經過Timer啓動定時任務,或者經過像Spring Task和quartz這樣的第三方任務調度框架也能夠開啓多線程任務。java

一、繼承Thread類建立線程git

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

二、實現Runnable接口建立線程
因爲Java是單繼承機制,若是本身的類已經繼承自另外一個類,則沒法再直接繼承Thread類,此時,能夠經過實現Runnable接口來實現多線程。異步

實現Runnable接口並實現其中的run方法,而後經過構造Thread實例,傳入Runnable實現類,而後調用Thread的start方法便可開啓一個新線程。ide

CreateThreadDemo2.javathis

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();

    }

}

輸出結果:spa

當前運行的線程名爲: MyThread1
當前運行的線程名爲: MyThread2

三、實現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

四、使用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毫秒】

五、其餘建立線程的方式

固然,除了以上四種主要的線程建立方式以外,也還有不少其餘的方式能夠啓動多線程任務。好比經過Timer啓動定時任務,或者經過像Spring Task和quartz這樣的第三方任務調度框架也能夠開啓多線程任務,關於第三方任務調度框架的例子還請查詢相關資料。

 

源碼下載

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


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

相關文章
相關標籤/搜索