Java多線程理解及實現


一、什么是線程?

 線程是操作系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。簡單理解就好比我們坐高鐵、飛機過安檢一樣,過安檢的時候一個入口只有一個安檢口,而多線程就是為安檢開啟了多個安檢口。Java在語言層面對多線程提供了卓越的支持。

二、線程和進程有什么區別?

 線程是進程的子集,一個進程可以有很多線程,每條線程並行執行不同的任務。不同的進程使用不同的內存空間,而所有的線程共享一片相同的內存空間。每個線程都擁有單獨的棧內存用來存儲本地數據。

三、 如何在Java中實現線程?

線程實現的三種方式:

  • 繼承Thread類創建線程類

  步驟:

===> 創建一個繼承於Thread類的子類 

===> 重寫Thread類的run()方法

===> 創建Thread類的子類的對象

===> 通過此對象調用start()方法啟動線程

public class FirstThreadTest extends Thread{
    //重寫run方法,run方法內就是線程要執行的業務邏輯代碼
    public void run(){
        for(int i = 0;i<100;i++){
            System.out.println(getName()+" "+i);
        }
    }
    public static void main(String[] args){
        for(int i = 0;i< 100;i++){
            // Thread.currentThread()方法返回當前正在執行的線程對象,getName()方法返回線程名稱,此處線程暫未開啟,所以打印的是Main線程名
            System.out.println(Thread.currentThread().getName()+" :"+i);
            if(i==20){
                // 啟動連個線程
                new FirstThreadTest().start();
                new FirstThreadTest().start();
            }
        }
    }
}
  •  通過Runnable接口創建線程類 (常用)

  步驟:

===>  定義Runnable接口實現類,並重寫run()方法【和Thread類中的run()方法是一樣的,都是來自Runnable接口的】

===>  創建實現類的對象,將實現類的對象作為參數傳遞到Thread類的構造器中

===>  通過Thread類對象調用start()方法啟動線程

public class RunnableThreadTest implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {
                // 創建實現類對象
                RunnableThreadTest rtt = new RunnableThreadTest();
                // 以參數的形式傳入Thread類的構造器中,並給當前線程重命名
                new Thread(rtt, "新線程1").start();
                new Thread(rtt, "新線程2").start();
            }
        }
    }
}
  •  通過線程池實現多線程 (最常用且最實用)

  在Java中juc包中有一個Executors工具類,可以為我們創建一個線程池,其本質就是new了一個ThreadPoolExecutor對象。

  1. Executors.newCachedThreadPool()(無界線程池,可以進行自動線程回收)
  2. Executors.newFixedThreadPool(int)(固定大小線程池)
  3. Executors.newSingleThreadExecutor()(單個后台線程)
  4. Executors.newScheduledThreadPool() (創建固定大小的線程,可以延遲或定時的執行任務)

 線程池體系結構簡化圖:

Tips:藍色實線箭頭是類的繼承;綠色虛線箭頭是接口的實現,綠色實線箭頭是接口的繼承 

  Executors 工具類通過提供一系列的工廠方法來創建線程池,返回的線程池(就是ThreadPoolExecutor對象)都實現了 ExecutorService 接口,ExecutorService接口繼承了Executor接口,提供了更豐富的方法來管理線程池,ExecutorService對象可以通過execute(Runnable)或 submit(Callable)方法來開始執行新的方法。

  ExecutorService(其實就是線程池)的生命周期包括三種狀態,運行,關閉,終止。創建后便進入運行狀態。調用shutdown() 方法就進入關閉狀態。此時ExecutorService不再接受新的任務,但是它還在執行已經提交的任務,等到所有的任務都執行完畢后,就到了終止狀態。

示例代碼:

public class ScheduledThreadTest  {
    public static void main(String[] args) {
        System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》start");
        // 創建支持計划任務的線程池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
        // delay:延遲   延遲兩秒執行任務
        threadPool.schedule(() -> System.out.println("定時任務"),2, TimeUnit.SECONDS);
        System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》end");
        threadPool.shutdown();
    }
}

輸出結果:(通過實際輸出,可以看到 ,兩秒鍾之后,才開始執行任務當中的打印 語句)

 =============================

周期性的定時任務示例代碼:

public class ScheduledThreadTest2 {
    public static void main(String[] args) {
        System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》start");
        // 創建支持計划任務的線程池
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2);
        /**
         *       scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit)
         *         第一個command參數是任務實例,
         *         第二個initialDelay參數是初始化延遲時間,
         *         第三個delay參數是延遲間隔時間,
         *         第四個unit參數是時間單元。
         */
        threadPool.scheduleWithFixedDelay(new Runnable() {
            int count = 0;
            @Override
            public void run() {
                System.out.println("count = " + count++);
            }
        }, 3, 2, TimeUnit.SECONDS);
        System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》end");
//        threadPool.shutdown(); // 不能調用此方法,否則定時任務無法啟動(延期3秒啟動,但是線程池已經關閉了)
    }
}

輸出結果:

…..后面每間隔2s,輸出一次。


 Callable接口

  現在我們知道了怎么創建線程:一種是通過創建Thread類,另一種是通過使用Runnable創建線程。但是,Runnable缺少的一項功能是,當線程終止時(即run()完成時),我們無法使線程返回結果。為了支持此功能,Java中提供了Callable,Callable接口使用泛型去定義它的返回類型。

  • 為了實現Runnable,需要實現不返回任何內容的run()方法,而對於Callable,需要實現在完成時返回結果的call()方法。請注意,不能使用Callable創建線程,只能使用Runnable創建線程。
  • 另一個區別是call()方法允許拋出異常,而run()則不能。
  • 為實現Callable而必須重寫call方法。

Future接口

  當call()方法完成時,結果必須存儲在主線程已知的對象中,以便主線程可以知道該線程返回的結果。為此,可以使用Future對象。將Future視為保存結果的對象–它可能暫時不保存結果,但將來會保存(一旦Callable返回)。因此,Future基本上是主線程可以跟蹤進度以及其他線程的結果的一種方式。

  Executors工具類提供了一些有用的方法在線程池中執行Callable內的任務。由於Callable任務是並行的(並行就是整體看上去是並行的,其實在某個時間點只有一個線程在執行),我們必須等待它返回的結果。 java.util.concurrent.Future對象為我們解決了這個問題。在線程池提交Callable任務后返回了一個Future對象,使用它可以知道Callable任務的狀態和得到Callable返回的執行結果。Future提供了get()方法讓我們可以等待Callable結束並獲取它的執行結果。

  要實現此接口,必須重寫5種方法:

 

   要創建線程,需要Runnable。為了獲得結果,需要future。Java庫具有具體的FutureTask類型,該類型實現Runnable和Future,並將這兩種功能組合在一起。可以通過為其構造函數提供Callable來創建FutureTask。然后,將FutureTask對象提供給Thread的構造函數以創建Thread對象。因此,間接地使用Callable創建線程。

 使用Callable和Future的完整示例:

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

public class TestCallable implements Callable<Object> {

    private int taskNum;

    public TestCallable(int taskNum) {
        this.taskNum = taskNum;
    }

    //1,2主要區別是創建線程的方式
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();

    }

    /**
     * 使用Executors.newFixedThreadPool創建線程池
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private static void test1() throws InterruptedException, ExecutionException {
        System.out.println("----程序開始運行----");
        Date date1 = new Date();
        int taskSize = 5;
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
            Callable c = new TestCallable(i);
            // 執行任務並獲取Future對象
            Future f = pool.submit(c);
            list.add(f);
        }
        // 關閉線程池
        pool.shutdown();
        // 獲取所有並發任務的運行結果
        for (Future f : list) {
            // 從Future對象上獲取任務的返回值,並輸出到控制台
            System.out.println(">>>" + f.get().toString()); //OPTION + return 拋異常
        }
        Date date2 = new Date();
        System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】");
    }

    /**
     * 線程直接使用new Thread來創建
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void test2() throws ExecutionException, InterruptedException {
        System.out.println("----程序開始運行----");
        Date date1 = new Date();
        int taskSize = 5;
        FutureTask[] randomNumberTasks = new FutureTask[5];
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < randomNumberTasks.length; i++) {
            Callable c = new TestCallable(i);
            // 執行任務並獲取Future對象
            randomNumberTasks[i] = new FutureTask(c);
            Thread t = new Thread(randomNumberTasks[i]);
            t.start();
        }

        // 獲取所有並發任務的運行結果
        for (Future f : randomNumberTasks) {
            // 從Future對象上獲取任務的返回值,並輸
            System.out.println(">>>" + f.get().toString()); //OPTION + return 拋異常
        }
        Date date2 = new Date();
        System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】");
    }

    /**
     * call方法的實現,主要用於執行線程的具體實現,並返回結果
     * @return
     * @throws Exception
     */
    @Override
    public Object call() throws Exception {
        System.out.println(">>>" + taskNum + "任務啟動");
        Date dateTmp1 = new Date();
        Thread.sleep(1000);
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        System.out.println(">>>" + taskNum + "任務終止");
        return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】";
    }
}

運行結果:

 


免責聲明!

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



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