最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java開(kāi)發(fā)異步編程中常用的接口和類(lèi)示例詳解

 更新時(shí)間:2025年08月11日 11:29:58   作者:羨寒.  
在現(xiàn)代Java中異步編程通常通過(guò)不同的機(jī)制和接口來(lái)實(shí)現(xiàn),這篇文章主要介紹了Java開(kāi)發(fā)異步編程中常用的接口和類(lèi)的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),+需要的朋友可以參考下

Callable 與 Runnable接口

  1. Callable接口類(lèi)似于Runnable接口,都是用于實(shí)現(xiàn)一個(gè)線程任務(wù)。
  2. Callable實(shí)現(xiàn)的線程可以返回執(zhí)行結(jié)果,但是Runnable不可以返回執(zhí)行結(jié)果。
  3. Callable的run方法會(huì)拋出編譯時(shí)異常,而Runnable沒(méi)有拋出異常。
  4. 可以使用工廠類(lèi)Executors把Runnable包裝成Callable。

Runnable使用方式一:使用Thread運(yùn)行

public class ThreadCreationDemo {

    // 第一種方式需要一個(gè)實(shí)現(xiàn)了Runnable接口的類(lèi)
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 打印當(dāng)前執(zhí)行此代碼的線程的名稱(chēng)
            System.out.println("方法一 (MyRunnable): 線程 " + Thread.currentThread().getName() + " 正在運(yùn)行。");
        }
    }

    public static void main(String[] args) {


        // 第一種方式: 使用一個(gè)獨(dú)立的類(lèi)去實(shí)現(xiàn)Runnable接口
        // 這種方法結(jié)構(gòu)清晰,適用于邏輯比較復(fù)雜的場(chǎng)景。
        Thread t1 = new Thread(new MyRunnable());
        t1.start();

        // 第二種方式: 使用Lambda表達(dá)式實(shí)現(xiàn)一個(gè)Runnable
        // 這是Java 8+中最推薦的簡(jiǎn)潔寫(xiě)法。
        Thread t2 = new Thread(() -> {
            System.out.println("方法二 (Lambda):   線程 " + Thread.currentThread().getName() + " 正在運(yùn)行。");
        });
        t2.start();

        // 第三種方式: 使用匿名內(nèi)部類(lèi)實(shí)現(xiàn)一個(gè)Runnable
        // 這是在Java 8出現(xiàn)之前常用的方法。
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("方法三 (匿名內(nèi)部類(lèi)): 線程 " + Thread.currentThread().getName() + " 正在運(yùn)行。");
            }
        });
        t3.start();
    }
}

Runnable使用方式二:使用線程池執(zhí)行

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorServiceDemo {

    // 定義一個(gè)實(shí)現(xiàn)了Runnable接口的類(lèi),用于作為要執(zhí)行的任務(wù)
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("任務(wù)由線程: " + Thread.currentThread().getName() + " 執(zhí)行。");
            try {
                // 模擬任務(wù)執(zhí)行耗時(shí)
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {

        // 第二種方式: 使用“線程池”去運(yùn)行Runnable
        // 使用Executors工廠類(lèi)創(chuàng)建一個(gè)單線程的線程池
        // 這個(gè)線程池保證任務(wù)是按提交順序依次執(zhí)行的
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        System.out.println("使用 executorService.execute() 提交任務(wù)。");
        // execute(Runnable): 提交一個(gè)任務(wù),沒(méi)有返回值。
        executorService.execute(new MyRunnable());

        System.out.println("使用 executorService.submit() 提交任務(wù)。");
        // submit(Runnable): 同樣提交一個(gè)任務(wù),但會(huì)返回一個(gè)Future對(duì)象。
        // 這個(gè)Future對(duì)象可以用來(lái)判斷任務(wù)是否執(zhí)行完畢.
        Future<?> future = executorService.submit(new MyRunnable());

        // --- 把Runnable通過(guò)工具類(lèi)轉(zhuǎn)成Callable ---
        // Callable與Runnable類(lèi)似,但它可以有返回值并能拋出異常。
        // Executors.callable()方法可以將一個(gè)Runnable轉(zhuǎn)換為Callable,
        // 其執(zhí)行結(jié)果為null。
        Callable<Object> callable = Executors.callable(new MyRunnable());
        System.out.println("提交轉(zhuǎn)換后的Callable任務(wù)。");
        Future<Object> callableFuture = executorService.submit(callable);


        // --- 關(guān)閉線程池 ---
        executorService.shutdown();

    }
}

Callable使用方式一:包裝到FutureTask里面,然后通過(guò)Thread運(yùn)行

因?yàn)镕utureTask實(shí)現(xiàn)了Runnable接口,所以可以作為參數(shù)傳入Thread中

一般寫(xiě)法

代碼示例:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureTaskDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        
        // 1. 創(chuàng)建一個(gè)有返回值的任務(wù) (Callable)
        Callable<String> myTask = () -> {
            System.out.println("子線程: 正在執(zhí)行任務(wù)...");
            Thread.sleep(2000); // 模擬耗時(shí)2秒
            return "任務(wù)執(zhí)行完畢,這是結(jié)果";
        };

        // 2. 將任務(wù)包裝成 FutureTask (如圖所示)
        FutureTask<String> futureTask = new FutureTask<>(myTask);

        // 3. 把 FutureTask 交給一個(gè)新線程去執(zhí)行 (如圖所示)
        // 因?yàn)?FutureTask 實(shí)現(xiàn)了 Runnable 接口,所以 Thread 可以直接運(yùn)行它
        Thread thread = new Thread(futureTask);
        thread.start();

        System.out.println("主線程: 已啟動(dòng)子線程,我先忙別的...");

        // 4. 在主線程中獲取任務(wù)結(jié)果
        // get() 方法會(huì)阻塞當(dāng)前線程,直到子線程的任務(wù)完成
        System.out.println("主線程: 現(xiàn)在需要結(jié)果了,開(kāi)始等待...");
        String result = futureTask.get(); // 等待并獲取結(jié)果

        System.out.println("主線程: 終于拿到了結(jié)果 - \"" + result + "\"");
    }
}

運(yùn)行結(jié)果:

主線程: 已啟動(dòng)子線程,我先忙別的...
子線程: 正在執(zhí)行任務(wù)...
主線程: 現(xiàn)在需要結(jié)果了,開(kāi)始等待...
(程序在這里會(huì)暫停2秒)
主線程: 終于拿到了結(jié)果 - "任務(wù)執(zhí)行完畢,這是結(jié)果"

也可以用匿名內(nèi)部類(lèi)和Lambda表達(dá)式來(lái)定義Callable任務(wù)

匿名內(nèi)部類(lèi)寫(xiě)法

匿名內(nèi)部類(lèi)是在Java 8之前的標(biāo)準(zhǔn)寫(xiě)法,語(yǔ)法上會(huì)顯得比較冗長(zhǎng)。

// 使用匿名內(nèi)部類(lèi)來(lái)定義 Callable 任務(wù)
FutureTask<String> futureTask_anonymous = new FutureTask<>(new Callable<String>() {
    @Override
    public String call() throws Exception {
        System.out.println("通過(guò)匿名內(nèi)部類(lèi)執(zhí)行...");
        Thread.sleep(1000);
        return "匿名內(nèi)部類(lèi)的結(jié)果";
    }
});

// 然后同樣交給 Thread 或線程池執(zhí)行
// Thread thread = new Thread(futureTask_anonymous);
// thread.start();

Lambda表達(dá)式寫(xiě)法

Lambda表達(dá)式是Java 8及以后推薦的函數(shù)式編程寫(xiě)法,非常簡(jiǎn)潔。

由于Callable接口是一個(gè)函數(shù)式接口(只有一個(gè)抽象方法call()),所以可以被Lambda表達(dá)式替代。

// 使用Lambda表達(dá)式來(lái)定義 Callable 任務(wù)
FutureTask<String> futureTask_lambda = new FutureTask<>(() -> {
    System.out.println("通過(guò)Lambda表達(dá)式執(zhí)行...");
    Thread.sleep(1000);
    return "Lambda的結(jié)果";
});

// 然后同樣交給 Thread 或線程池執(zhí)行
// Thread thread = new Thread(futureTask_lambda);
// thread.start();

Callable使用方式二:使用線程池執(zhí)行

execute方法只能接收Runnable對(duì)象,所以Callable對(duì)象不能使用線程池的execute方法執(zhí)行,只能使用submit方法執(zhí)行。

執(zhí)行會(huì)返回Future類(lèi)的對(duì)象,對(duì)這個(gè)對(duì)象調(diào)用get()方法即可以拿到返回值。

示例代碼:

import java.util.concurrent.*;

public class ExecutorServiceDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 1. 創(chuàng)建一個(gè)線程池 (圖中是單線程的,更常用的是固定大小的線程池)
        ExecutorService executorService = Executors.newFixedThreadPool(2); // 例如,一個(gè)有兩個(gè)線程的池

        System.out.println("主線程: 準(zhǔn)備將任務(wù)提交給線程池。");

        // 2. 將 Callable 任務(wù)提交給線程池 (直接使用Lambda表達(dá)式定義任務(wù))
        // submit() 方法會(huì)立即返回一個(gè) Future 對(duì)象
        Future<String> future = executorService.submit(() -> {
            System.out.println("子線程 (" + Thread.currentThread().getName() + "): 開(kāi)始執(zhí)行耗時(shí)任務(wù)...");
            Thread.sleep(2000); // 模擬耗時(shí)2秒
            return "任務(wù)完成,這是來(lái)自線程池的結(jié)果";
        });

        System.out.println("主線程: 任務(wù)已提交,我可以繼續(xù)做其他事情...");
        // 在這里,主線程可以執(zhí)行其他不受阻塞的代碼

        // 3. 在需要結(jié)果時(shí),調(diào)用 future.get() 阻塞等待
        System.out.println("主線程: 現(xiàn)在需要結(jié)果了,開(kāi)始等待...");
        String result = future.get(); // 阻塞,直到子線程任務(wù)完成并返回結(jié)果

        System.out.println("主線程: 成功獲取到結(jié)果 -> \"" + result + "\"");

        // 4. 關(guān)閉線程池 (這是必須的步驟,否則JVM不會(huì)退出)
        executorService.shutdown();
    }
}

Future接口

  1. 一個(gè)Future表示一個(gè)異步任務(wù)的結(jié)果;
  2. 它提供了一些方法用來(lái)檢查異步是不是已經(jīng)完成,沒(méi)有完成就等待,待其完成后取回異步執(zhí)行的結(jié)果;
  3. 異步執(zhí)行的結(jié)果,只能通過(guò)get()方法獲取,get()方法是阻塞的,如果異步執(zhí)行沒(méi)有執(zhí)行結(jié)束 ,則阻塞直至拿到結(jié)果;
  4. 它提供了一個(gè)cancel()方法用于取消異步執(zhí)行,執(zhí)行完畢后不可取消;
  5. 如果不想要返回的結(jié)果,也可以把底層的任務(wù)聲明為返回null。

Future接口有5個(gè)方法:

  • boolean isDone(): 判斷任務(wù)是否已經(jīng)完成。
  • V get(): 阻塞等待,直到任務(wù)完成并返回結(jié)果。
  • V get(long timeout, TimeUnit unit): 在指定時(shí)間內(nèi)阻塞等待,超時(shí)則拋出TimeoutException。
  • boolean isCancelled(): 判斷任務(wù)是否在完成前被取消。
  • boolean cancel(boolean mayInterruptIfRunning): 嘗試取消任務(wù)的執(zhí)行。
import java.util.concurrent.*;

public class FutureMethodsDemo {

    public static void main(String[] args) {

        // 創(chuàng)建一個(gè)固定大小為2的線程池
        ExecutorService executor = Executors.newFixedThreadPool(2);

        System.out.println("--- 演示 get(), isDone() ---");
        // 提交一個(gè) Callable 任務(wù),該任務(wù)會(huì)模擬2秒的計(jì)算,然后返回一個(gè)字符串結(jié)果
        Future<String> future1 = executor.submit(() -> {
            System.out.println("任務(wù)1: 開(kāi)始執(zhí)行,預(yù)計(jì)耗時(shí)2秒...");
            Thread.sleep(2000);
            return "任務(wù)1執(zhí)行完畢!";
        });

        // 1. isDone()
        // 在任務(wù)剛提交時(shí),檢查它是否完成
        System.out.println("提交任務(wù)1后,立即檢查 isDone(): " + future1.isDone());

        try {
            // 2. get()
            // 這是一個(gè)阻塞方法,主線程會(huì)在這里等待,直到任務(wù)1執(zhí)行完成并返回結(jié)果
            System.out.println("調(diào)用 future1.get(),主線程等待中...");
            String result1 = future1.get();
            System.out.println("future1.get() 獲得結(jié)果: \"" + result1 + "\"");

            // 任務(wù)完成后,再次檢查isDone()
            System.out.println("任務(wù)1完成后,再次檢查 isDone(): " + future1.isDone());

        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("\n--- 演示 get(timeout, unit) ---");
        // 提交一個(gè)需要3秒才能完成的任務(wù)
        Future<String> future2 = executor.submit(() -> {
            System.out.println("任務(wù)2: 開(kāi)始執(zhí)行,預(yù)計(jì)耗時(shí)3秒...");
            Thread.sleep(3000);
            return "任務(wù)2執(zhí)行完畢!";
        });

        try {
            // 3. get(long timeout, TimeUnit unit)
            // 我們只等待1秒,由于任務(wù)需要3秒,所以這必定會(huì)超時(shí)
            System.out.println("調(diào)用 future2.get(1, TimeUnit.SECONDS),主線程最多等待1秒...");
            String result2 = future2.get(1, TimeUnit.SECONDS);
            System.out.println("在1秒內(nèi)獲取到結(jié)果: " + result2);
        } catch (TimeoutException e) {
            // 這里會(huì)捕獲到超時(shí)異常
            System.out.println("等待超時(shí)! " + e.getClass().getName());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }


        System.out.println("\n--- 演示 cancel(), isCancelled() ---");
        // 提交一個(gè)長(zhǎng)時(shí)間運(yùn)行的任務(wù)
        Future<String> future3 = executor.submit(() -> {
            System.out.println("任務(wù)3: 開(kāi)始執(zhí)行,這是一個(gè)可能被中斷的長(zhǎng)任務(wù)...");
            try {
                Thread.sleep(5000); // 模擬一個(gè)非常耗時(shí)的操作
            } catch (InterruptedException e) {
                // 如果任務(wù)被中斷,會(huì)進(jìn)入這里
                System.out.println("任務(wù)3: 執(zhí)行被中斷!");
                return "已被中斷";
            }
            return "任務(wù)3正常完成";
        });
        
        // 為了確保 cancel() 在任務(wù)開(kāi)始后執(zhí)行,我們稍微等待一下
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 4. cancel(boolean mayInterruptIfRunning)
        // 嘗試取消任務(wù)的執(zhí)行。參數(shù)true表示如果任務(wù)已經(jīng)在運(yùn)行,就中斷執(zhí)行它的線程。
        System.out.println("嘗試取消任務(wù)3...");
        boolean cancelResult = future3.cancel(true);
        System.out.println("cancel(true) 方法返回: " + cancelResult);

        // 5. isCancelled()
        // 檢查任務(wù)是否已經(jīng)被成功取消
        System.out.println("調(diào)用cancel后,檢查 isCancelled(): " + future3.isCancelled());

        // 再次檢查isDone()。一個(gè)被取消的任務(wù)也被認(rèn)為是“完成”的。
        System.out.println("調(diào)用cancel后,檢查 isDone(): " + future3.isDone());
        
        try {
            // 試圖獲取一個(gè)已取消任務(wù)的結(jié)果,會(huì)拋出 CancellationException
            System.out.println("嘗試 get() 已取消的任務(wù)3...");
            String result3 = future3.get();
            System.out.println("獲取到已取消任務(wù)的結(jié)果: " + result3);
        } catch (CancellationException e) {
            System.out.println("獲取結(jié)果失敗,因?yàn)槿蝿?wù)已被取消! " + e.getClass().getName());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 關(guān)閉線程池,這是一個(gè)好習(xí)慣
        executor.shutdownNow();
    }
}

FutureTask類(lèi)

基本特點(diǎn)

  1. FutureTask類(lèi)實(shí)現(xiàn)了Runnable接口、Future接口(RunnableFuture接口)。
  2. FutureTask類(lèi)除了實(shí)現(xiàn)接口的5+1個(gè)方法,還有兩個(gè)構(gòu)造方法。
  3. FutureTask代表了一個(gè)可以取消的異步執(zhí)行。
  4. FutureTask可以用來(lái)包裝Runnable或者Callable。
  5. FutureTask可以提交到Executor中去執(zhí)行。
import java.util.concurrent.*;

public class FutureTaskFeaturesDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // =================================================================
        // 特點(diǎn) 5: FutureTask 可以用來(lái)包裝 Runnable 或者 Callable
        // =================================================================
        // 創(chuàng)建一個(gè) Callable 任務(wù),它會(huì)模擬計(jì)算并返回一個(gè)結(jié)果
        Callable<String> callableTask = () -> {
            System.out.println("子線程 (來(lái)自Callable): 正在進(jìn)行復(fù)雜的計(jì)算...");
            Thread.sleep(2000); // 模擬耗時(shí)操作
            return "計(jì)算完成,結(jié)果是ABC";
        };

        // 創(chuàng)建一個(gè) Runnable 任務(wù),它沒(méi)有返回值
        Runnable runnableTask = () -> {
            System.out.println("子線程 (來(lái)自Runnable): 正在執(zhí)行一個(gè)任務(wù)...");
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };

        // 使用 Callable 構(gòu)造 FutureTask
        FutureTask<String> futureTaskFromCallable = new FutureTask<>(callableTask);

        // 使用 Runnable 構(gòu)造 FutureTask,需要提供一個(gè)默認(rèn)的返回結(jié)果
        String defaultResult = "Runnable執(zhí)行完畢";
        FutureTask<String> futureTaskFromRunnable = new FutureTask<>(runnableTask, defaultResult);


        // =================================================================
        // 特點(diǎn) 2 & 6: FutureTask 是一個(gè)實(shí)現(xiàn)了 Runnable 和 Future 的類(lèi),可以提交到 Executor 執(zhí)行
        // =================================================================
        // 因?yàn)?FutureTask 實(shí)現(xiàn)了 Runnable,所以它可以被線程池執(zhí)行
        ExecutorService executor = Executors.newFixedThreadPool(2);

        System.out.println("主線程: 將 futureTaskFromCallable 提交到線程池執(zhí)行。");
        executor.submit(futureTaskFromCallable); // 特點(diǎn)6的演示

        // =================================================================
        // 特點(diǎn) 1 & 3: 代表可取消的異步計(jì)算,結(jié)果只能在執(zhí)行完成后才能獲取 (get() 會(huì)阻塞)
        // =================================================================
        System.out.println("主線程: 嘗試獲取 futureTaskFromCallable 的結(jié)果...");

        // 在任務(wù)完成前,isDone() 返回 false (特點(diǎn)2的演示)
        System.out.println("主線程: 任務(wù)完成了嗎? " + futureTaskFromCallable.isDone());

        // 調(diào)用 get() 方法,主線程會(huì)在這里阻塞,直到子線程中的任務(wù)執(zhí)行完畢 (特點(diǎn)3的演示)
        String result = futureTaskFromCallable.get(); // 阻塞點(diǎn)

        System.out.println("主線程: 終于等到了結(jié)果 - " + result); // 特點(diǎn)1的演示

        // 任務(wù)完成后,isDone() 返回 true
        System.out.println("主線程: 任務(wù)現(xiàn)在完成了嗎? " + futureTaskFromCallable.isDone());
        System.out.println("------------------------------------------");

        // =================================================================
        // 特點(diǎn) 4: 一旦執(zhí)行完成,則不能重新開(kāi)始執(zhí)行,也不能取消
        // =================================================================
        System.out.println("主線程: 演示任務(wù)完成后的狀態(tài)。");

        // 嘗試取消一個(gè)已經(jīng)完成的任務(wù)
        boolean cancelResult = futureTaskFromCallable.cancel(true);
        System.out.println("主線程: 嘗試取消已完成的任務(wù),結(jié)果: " + cancelResult);
        System.out.println("主線程: 已完成的任務(wù)被取消了嗎? " + futureTaskFromCallable.isCancelled());

        // 再次提交已經(jīng)完成的任務(wù)到線程池
        // 注意:這不會(huì)讓任務(wù)的 run() 方法被再次執(zhí)行。
        // FutureTask 內(nèi)部有狀態(tài)控制,一旦任務(wù)完成(正常、異?;蛉∠臓顟B(tài)就不會(huì)再改變。
        System.out.println("主線程: 再次提交同一個(gè)已完成的FutureTask...");
        executor.submit(futureTaskFromCallable); // 提交是有效的,但任務(wù)的run方法不會(huì)再執(zhí)行
        // 你可以再次 get(), 它會(huì)立刻返回之前已經(jīng)計(jì)算好的結(jié)果,而不會(huì)重新計(jì)算。
        System.out.println("主線程: 再次get(),立即返回結(jié)果: " + futureTaskFromCallable.get());
        System.out.println("------------------------------------------");


        // 演示取消一個(gè)尚未完成的任務(wù) (再次體現(xiàn)特點(diǎn)1)
        FutureTask<String> longRunningTask = new FutureTask<>(() -> {
            System.out.println("子線程 (長(zhǎng)任務(wù)): 我要睡10秒,除非被中斷...");
            Thread.sleep(10000);
            return "我睡醒了";
        });

        executor.submit(longRunningTask);
        Thread.sleep(100); // 確保任務(wù)已經(jīng)開(kāi)始運(yùn)行

        System.out.println("主線程: 任務(wù)太慢了,決定取消它。");
        longRunningTask.cancel(true); // 嘗試取消

        System.out.println("主線程: 長(zhǎng)任務(wù)被取消了嗎? " + longRunningTask.isCancelled());
        System.out.println("主線程: 長(zhǎng)任務(wù)算'完成'了嗎? " + longRunningTask.isDone());
        
        try {
            longRunningTask.get(); // 對(duì)已取消的任務(wù)調(diào)用get會(huì)拋出CancellationException
        } catch (CancellationException e) {
            System.out.println("主線程: 果然,獲取結(jié)果時(shí)拋出了 " + e.getClass().getSimpleName());
        }

        // 關(guān)閉線程池
        executor.shutdown();
    }
}

“不可重復(fù)執(zhí)行”特性(含源碼)

還有一點(diǎn)需要注意的就是,一旦執(zhí)行完成,就不能重新開(kāi)始執(zhí)行,也不能取消。

FutureTask之所以再次執(zhí)行沒(méi)有效果,是因?yàn)樗鼉?nèi)部設(shè)計(jì)了一個(gè)“一次性”的狀態(tài)機(jī)(State Machine)。一旦任務(wù)進(jìn)入“完成”狀態(tài)(無(wú)論是正常結(jié)束、異常終止還是被取消),它的狀態(tài)就無(wú)法再回到“未開(kāi)始”狀態(tài)。

示例如下:

錯(cuò)誤的做法:

Callable<String> taskLogic = () -> "Hello";
FutureTask<String> myTask = new FutureTask<>(taskLogic);

executor.submit(myTask); // 第一次執(zhí)行,有效
executor.submit(myTask); // 第二次提交同一個(gè)對(duì)象,無(wú)效

正確的做法:

Callable<String> taskLogic = () -> "Hello"; // 業(yè)務(wù)邏輯可以復(fù)用

// 第一次執(zhí)行
FutureTask<String> task1 = new FutureTask<>(taskLogic);
executor.submit(task1);
System.out.println(task1.get());

// 第二次執(zhí)行,必須創(chuàng)建新對(duì)象
FutureTask<String> task2 = new FutureTask<>(taskLogic);
executor.submit(task2);
System.out.println(task2.get());

為什么會(huì)有這種現(xiàn)象?下面對(duì)原理進(jìn)行分析:

FutureTask被設(shè)計(jì)的首要目的不僅僅是“去執(zhí)行一個(gè)任務(wù)”,更是“持有(或代表)一個(gè)異步計(jì)算的結(jié)果”。

可以將其想象成一張一次性的彩票

  • 創(chuàng)建FutureTask: 買(mǎi)了一張彩票,彩票處于“未開(kāi)獎(jiǎng)”(NEW)狀態(tài)。
  • 執(zhí)行run()方法: 開(kāi)獎(jiǎng)過(guò)程開(kāi)始了。這是彩票唯一一次被“使用”的機(jī)會(huì)。
  • 執(zhí)行完成: 開(kāi)獎(jiǎng)結(jié)束,彩票狀態(tài)變?yōu)?ldquo;已開(kāi)獎(jiǎng)”(NORMAL / EXCEPTIONAL),結(jié)果(中獎(jiǎng)或未中獎(jiǎng))也已經(jīng)確定并記錄在彩票上。
  • 調(diào)用get(): 隨時(shí)可以查看這張彩票的開(kāi)獎(jiǎng)結(jié)果。
  • 再次執(zhí)行: 能用同一張已經(jīng)開(kāi)過(guò)獎(jiǎng)的彩票去參加下一輪的抽獎(jiǎng)嗎?顯然不能。它的使命已經(jīng)在第一次開(kāi)獎(jiǎng)時(shí)完成了。

FutureTask也是如此。它的run()方法被設(shè)計(jì)為最多只執(zhí)行一次。一旦執(zhí)行完畢,它就從一個(gè)“任務(wù)執(zhí)行器”轉(zhuǎn)變?yōu)橐粋€(gè)“結(jié)果容器”。任何后續(xù)對(duì)get()的調(diào)用都會(huì)立刻返回已經(jīng)緩存的結(jié)果,而任何再次執(zhí)行它的嘗試都會(huì)被直接忽略。

FutureTask內(nèi)部通過(guò)一個(gè)state字段來(lái)管理其生命周期。這個(gè)狀態(tài)流轉(zhuǎn)是單向的,不可逆轉(zhuǎn)。

其主要狀態(tài)有:

  • NEW (0): 任務(wù)已創(chuàng)建,但尚未開(kāi)始執(zhí)行。這是唯一可以開(kāi)始執(zhí)行任務(wù)的狀態(tài)。
  • COMPLETING (1): 任務(wù)正在執(zhí)行中,即將完成但結(jié)果還未設(shè)置。這是一個(gè)臨時(shí)的中間狀態(tài)。
  • NORMAL (2): 任務(wù)已正常執(zhí)行完畢,結(jié)果已經(jīng)成功設(shè)置。
  • EXCEPTIONAL (3): 任務(wù)執(zhí)行過(guò)程中拋出了異常,異常信息已被保存。
  • CANCELLED (4): 任務(wù)在執(zhí)行完成前被取消。
  • INTERRUPTING (5): 任務(wù)正在被中斷的過(guò)程中。
  • INTERRUPTED (6): 任務(wù)已經(jīng)被成功中斷。

關(guān)鍵點(diǎn):一旦狀態(tài)從 NEW 變?yōu)槿魏纹渌麪顟B(tài)(如NORMAL, EXCEPTIONAL, CANCELLED),就再也無(wú)法回到 NEW 狀態(tài)。

FutureTaskrun()方法完美地體現(xiàn)了這種狀態(tài)檢查機(jī)制(以下為簡(jiǎn)化后的邏輯):

public void run() {
    // 1. 關(guān)鍵檢查:如果當(dāng)前狀態(tài)不是 NEW,或者設(shè)置執(zhí)行線程失?。ㄕf(shuō)明其他線程搶先執(zhí)行了),
    //    則直接返回,不執(zhí)行任何操作。
    if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;

    try {
        Callable<V> c = callable; // 獲取包裝的任務(wù)
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                // 2. 真正執(zhí)行任務(wù)
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                // 3. 如果有異常,設(shè)置異常結(jié)果,并將狀態(tài)變?yōu)?EXCEPTIONAL
                setException(ex);
            }
            if (ran)
                // 4. 如果正常完成,設(shè)置正常結(jié)果,并將狀態(tài)變?yōu)?NORMAL
                set(result);
        }
    } finally {
        // 清理工作
        runner = null;
        // ...
    }
}

從源碼可以看出,run()方法的第一行就是一個(gè)防御性檢查。如果你拿著一個(gè)已經(jīng)執(zhí)行過(guò)(state不再是NEW)的FutureTask實(shí)例去提交給線程池,線程池調(diào)用它的run()方法時(shí),這個(gè)檢查會(huì)直接失敗,方法立即return,任務(wù)的邏輯自然就不會(huì)被再次執(zhí)行了。

set()方法是狀態(tài)變更的開(kāi)始。

// FutureTask.java

protected void set(V v) {
    // 使用 CAS(Compare-And-Swap)原子地將狀態(tài)從 NEW 變?yōu)?COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // outcome 字段用于存儲(chǔ)最終結(jié)果
        this.outcome = v;
        // 將最終狀態(tài)設(shè)置為 NORMAL (正常完成)
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        
        // *** 關(guān)鍵調(diào)用!進(jìn)入收尾階段 ***
        finishCompletion();
    }
}
  • 這里有兩次狀態(tài)變更:首先原子地從NEW變成一個(gè)臨時(shí)的COMPLETING狀態(tài),這是為了防止并發(fā)的cancel()操作。
  • 然后設(shè)置outcome字段保存結(jié)果。
  • 最后,將狀態(tài)設(shè)置為最終的NORMAL,并調(diào)用finishCompletion()。

finishCompletion() —— 喚醒所有等待者,這個(gè)方法負(fù)責(zé)喚醒所有因調(diào)用get()而被阻塞的線程。

// FutureTask.java

private void finishCompletion() {
    // 'waiters' 是一個(gè)單向鏈表,存儲(chǔ)了所有正在等待結(jié)果的線程 (WaitNode)。
    for (WaitNode q; (q = waiters) != null;) {
        // 使用 CAS 將 'waiters' 鏈表頭置為 null,確保這個(gè)喚醒過(guò)程只被執(zhí)行一次。
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            // 遍歷整個(gè)等待者鏈表
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    // *** 喚醒! ***
                    // 使用 LockSupport.unpark() 喚醒那個(gè)正在等待的線程。
                    // 那個(gè)線程會(huì)從 get() -> awaitDone() 的阻塞中醒來(lái)。
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // help GC
                q = next;
            }
            break;
        }
    }

    // 做一些完成后的清理工作
    done();
    callable = null; // a micro-optimization
}
  • 核心邏輯: 該方法會(huì)原子地“摘下”整個(gè)等待者鏈表 (waiters)。
  • 然后,它會(huì)遍歷這個(gè)剛剛摘下的鏈表,并對(duì)其中的每一個(gè)節(jié)點(diǎn)所代表的線程,執(zhí)行LockSupport.unpark(t)。
  • unpark的線程之前正阻塞在awaitDone()方法里,現(xiàn)在它被喚醒,就可以從awaitDone()返回,接著從get()方法拿到outcome中存儲(chǔ)的結(jié)果,然后繼續(xù)執(zhí)行。

整個(gè)流程串起來(lái)就是:

  1. A線程調(diào)用futureTask.get()。get()發(fā)現(xiàn)任務(wù)未完成(stateNEW),于是將A線程包裝成一個(gè)WaitNode節(jié)點(diǎn),加入到waiters等待鏈表中,然后LockSupport.park()讓自身(A線程)掛起等待。
  2. B線程(線程池中的工作線程)開(kāi)始執(zhí)行futureTask.run()。
  3. run()方法檢查狀態(tài)為NEW,通過(guò)檢查,開(kāi)始執(zhí)行業(yè)務(wù)代碼。
  4. 業(yè)務(wù)代碼執(zhí)行完畢,run()調(diào)用set(result)
  5. set(result)stateNEW最終改為NORMAL,把結(jié)果存入outcome,然后調(diào)用finishCompletion()。
  6. finishCompletion()遍歷waiters鏈表,找到代表A線程的節(jié)點(diǎn),并調(diào)用LockSupport.unpark(A線程)。
  7. A線程被喚醒,從get()方法中返回,并讀取outcome字段中已經(jīng)準(zhǔn)備好的結(jié)果。

因?yàn)?code>state的狀態(tài)被永久地改變?yōu)榱?code>NORMAL,并且finishCompletion保證了所有等待者都被喚醒,這個(gè)FutureTask的使命就此終結(jié)。任何后續(xù)對(duì)run()的調(diào)用都會(huì)在第一步的if (state != NEW)檢查中失敗,從而實(shí)現(xiàn)了“一次性”的語(yǔ)義。

從原始FutureTask對(duì)象中取值(含源碼)

將一個(gè)已經(jīng)創(chuàng)建好的FutureTask對(duì)象提交給ExecutorService時(shí),submit方法會(huì)返回一個(gè)新的、包裝了你原始任務(wù)的Future對(duì)象。而計(jì)算的真正結(jié)果,仍然存儲(chǔ)在原始的那個(gè)FutureTask對(duì)象里。調(diào)用這個(gè)新的包裝對(duì)象的get()方法,往往得到的是null,而不是想要的結(jié)果。

代碼示例:

import java.util.concurrent.*;

public class FutureTaskGetDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 1. 創(chuàng)建一個(gè) Callable,這是我們真正的業(yè)務(wù)邏輯
        Callable<String> myCallable = () -> {
            System.out.println("子線程: 正在執(zhí)行核心業(yè)務(wù)邏輯...");
            Thread.sleep(1000);
            return "這是真正的計(jì)算結(jié)果";
        };

        // 2. 創(chuàng)建我們自己的、原始的 FutureTask 對(duì)象
        // 我們需要用這個(gè)對(duì)象來(lái)獲取最終結(jié)果
        FutureTask<String> originalFutureTask = new FutureTask<>(myCallable);

        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 3. 將我們?cè)嫉?FutureTask 提交給線程池
        // submit(Runnable) 會(huì)返回一個(gè)新的 Future 對(duì)象,我們稱(chēng)之為 wrapperFuture
        Future<?> wrapperFuture = executor.submit(originalFutureTask);

        // 4. 分別從兩個(gè) Future 對(duì)象中獲取結(jié)果

        // 錯(cuò)誤的做法:嘗試從 submit 方法返回的 Future 中獲取結(jié)果
        // 這個(gè) get() 等待的是 wrapperFuture 的完成,其結(jié)果是 null
        Object resultFromWrapper = wrapperFuture.get();
        System.out.println("從 submit() 返回的 wrapperFuture.get() 拿到的結(jié)果是: " + resultFromWrapper);
        System.out.println("wrapperFuture is done: " + wrapperFuture.isDone());

        System.out.println("-------------------------------------------------");

        // 正確的做法:從我們自己創(chuàng)建的原始 FutureTask 對(duì)象中獲取結(jié)果
        // 這個(gè) get() 獲取的是存儲(chǔ)在 originalFutureTask 內(nèi)部的真實(shí)結(jié)果
        String resultFromOriginal = originalFutureTask.get();
        System.out.println("從原始的 originalFutureTask.get() 拿到的結(jié)果是: " + resultFromOriginal);
        System.out.println("originalFutureTask is done: " + originalFutureTask.isDone());

        executor.shutdown();
    }
}

運(yùn)行結(jié)果:

子線程: 正在執(zhí)行核心業(yè)務(wù)邏輯...
從 submit() 返回的 wrapperFuture.get() 拿到的結(jié)果是: null
wrapperFuture is done: true
-------------------------------------------------
從原始的 originalFutureTask.get() 拿到的結(jié)果是: 這是真正的計(jì)算結(jié)果
originalFutureTask is done: true

從源碼的角度看這種原因:

第1步: 進(jìn)入ExecutorService.submit()

FutureTask本身實(shí)現(xiàn)了Runnable接口,所以這里實(shí)際調(diào)用的是submit(Runnable task)。我們看AbstractExecutorService中的實(shí)現(xiàn):

// AbstractExecutorService.java

public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    // 關(guān)鍵!它調(diào)用了 newTaskFor 來(lái)創(chuàng)建一個(gè)新的 RunnableFuture 對(duì)象。
    RunnableFuture<Void> ftask = newTaskFor(task, null); 
    // 然后執(zhí)行這個(gè)新創(chuàng)建的任務(wù)
    execute(ftask);
    // 最后返回這個(gè)新創(chuàng)建的任務(wù)
    return ftask;
}

初始的FutureTask被作為對(duì)象傳入,newTaskFor(originalFutureTask, null)被調(diào)用了。這個(gè)方法默認(rèn)會(huì)返回一個(gè)新的FutureTask實(shí)例

所以,代碼等價(jià)于:RunnableFuture<Void> ftask = new FutureTask<Void>(originalFutureTask, null);

現(xiàn)在你就有了兩個(gè)FutureTask

  1. originalFutureTask: 你自己創(chuàng)建的,它包裝了myCallable,期望的結(jié)果是String類(lèi)型。
  2. wrapperFuture (即ftask): submit方法內(nèi)部創(chuàng)建的,它包裝了originalFutureTask,期望的結(jié)果是null

第2步: 任務(wù)執(zhí)行

線程池實(shí)際執(zhí)行的是外層的wrapperFuture。

  1. wrapperFuture.run()被調(diào)用。
  2. wrapperFutureCallable是什么?是originalFutureTask自己(因?yàn)樗鼘?shí)現(xiàn)了Runnable)。所以,wrapperFuturerun方法內(nèi)部會(huì)調(diào)用originalFutureTask.run()。
  3. originalFutureTask.run()被調(diào)用。它會(huì)執(zhí)行最初的myCallable,計(jì)算出結(jié)果“真正的結(jié)果”。
  4. originalFutureTask執(zhí)行成功后,會(huì)調(diào)用set("真正的結(jié)果"),將結(jié)果保存在自己的outcome字段中。

第3步:finishCompletion()和關(guān)鍵的null操作

originalFutureTask在調(diào)用set()并最終進(jìn)入finishCompletion()方法后,會(huì)執(zhí)行一項(xiàng)重要的清理工作。

// FutureTask.java

private void finishCompletion() {
    // ... (喚醒等待者線程的代碼) ...

    done(); // 這是一個(gè)空方法,留給子類(lèi)擴(kuò)展

    // *** 最關(guān)鍵的一行 ***
    // 為了幫助垃圾回收,將內(nèi)部的 callable 引用置為 null。
    // 因?yàn)槿蝿?wù)已經(jīng)執(zhí)行完了,理論上不再需要它了。
    callable = null;
}

originalFutureTask完成它的使命后,它扔掉了對(duì)myCallable的引用。它的outcome字段已經(jīng)安全地保存了結(jié)果“真正的結(jié)果”。

第4步: 結(jié)果返回

  • 當(dāng)originalFutureTask.run()執(zhí)行完畢后,wrapperFuturerun()方法也隨之結(jié)束。
  • wrapperFuture也成功完成了,它會(huì)調(diào)用set(null)(因?yàn)閯?chuàng)建它的時(shí)候newTaskFor(task, null)的第二個(gè)參數(shù)是null)。所以wrapperFutureoutcome字段保存的是null

結(jié)論:

  • 如果你調(diào)用 wrapperFuture.get(),你得到的是wrapperFutureoutcome,也就是 null。
  • 如果你調(diào)用 originalFutureTask.get(),你得到的是originalFutureTaskoutcome,也就是 "真正的結(jié)果"

新返回Future對(duì)象的作用

wrapperFuture的核心價(jià)值在于提供統(tǒng)一的控制和狀態(tài)管理接口,而不是為了傳遞結(jié)果。它是ExecutorService框架為了保持API一致性和健壯性而設(shè)計(jì)的關(guān)鍵一環(huán)。

任務(wù)取消wrapperFuture最重要的用途之一。當(dāng)你把任務(wù)提交給ExecutorService后,你手中唯一能直接操作的句柄就是submit方法返回的wrapperFuture。你需要通過(guò)它來(lái)嘗試取消任務(wù)的執(zhí)行。

當(dāng)你在wrapperFuture上調(diào)用cancel()時(shí),它會(huì)將這個(gè)取消請(qǐng)求傳播給內(nèi)部包裝的originalFutureTask。

FutureTask.cancel()方法會(huì)檢查任務(wù)狀態(tài),如果任務(wù)還沒(méi)開(kāi)始跑,就將其狀態(tài)設(shè)置為CANCELLED。如果任務(wù)正在跑,它會(huì)根據(jù)你傳入的mayInterruptIfRunning參數(shù)來(lái)決定是否要中斷執(zhí)行該任務(wù)的線程。

import java.util.concurrent.*;

public class WrapperFutureCancelDemo {

    public static void main(String[] args) throws InterruptedException {
        FutureTask<String> originalFutureTask = new FutureTask<>(() -> {
            System.out.println("子線程: 任務(wù)開(kāi)始,準(zhǔn)備睡5秒...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // cancel(true) 會(huì)讓線程在這里拋出中斷異常
                System.out.println("子線程: 我被中斷了,任務(wù)提前結(jié)束!");
                return "未完成的結(jié)果";
            }
            System.out.println("子線程: 任務(wù)正常完成。");
            return "已完成的結(jié)果";
        });

        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 提交后,我們只能通過(guò) wrapperFuture 來(lái)控制這個(gè)任務(wù)
        Future<?> wrapperFuture = executor.submit(originalFutureTask);

        // 讓主線程睡1秒,確保子任務(wù)已經(jīng)開(kāi)始運(yùn)行
        Thread.sleep(1000);

        System.out.println("主線程: 任務(wù)已經(jīng)跑了1秒,現(xiàn)在決定取消它。");
        // 調(diào)用 wrapperFuture 的 cancel(true) 來(lái)中斷任務(wù)
        boolean cancelled = wrapperFuture.cancel(true); // true表示如果任務(wù)正在運(yùn)行,就中斷它

        System.out.println("主線程: 任務(wù)取消成功了嗎? " + cancelled);
        // isCancelled() 也會(huì)被傳播
        System.out.println("主線程: wrapperFuture 的狀態(tài)是否是已取消? " + wrapperFuture.isCancelled());
        System.out.println("主線程: originalFutureTask 的狀態(tài)是否是已取消? " + originalFutureTask.isCancelled());

        executor.shutdown();
    }
}

運(yùn)行結(jié)果:

子線程: 任務(wù)開(kāi)始,準(zhǔn)備睡5秒...
主線程: 任務(wù)已經(jīng)跑了1秒,現(xiàn)在決定取消它。
子線程: 我被中斷了,任務(wù)提前結(jié)束!
主線程: 任務(wù)取消成功了嗎? true
主線程: wrapperFuture 的狀態(tài)是否是已取消? true
主線程: originalFutureTask 的狀態(tài)是否是已取消? true

在這個(gè)例子中,完全沒(méi)有用到get(),但wrapperFuture.cancel()成功地管理了任務(wù)的生命周期。

雖然wrapperFuture.get()返回的是null,但它依然是一個(gè)阻塞方法。它的作用是讓當(dāng)前線程等待,直到任務(wù)執(zhí)行完成。這是一個(gè)非常重要的同步機(jī)制。

更重要的是,如果你的原始任務(wù)在執(zhí)行時(shí)拋出了異常,調(diào)用wrapperFuture.get()會(huì)重新拋出這個(gè)異常(包裝在ExecutionException中)。這使得主線程能夠捕獲并處理后臺(tái)任務(wù)的錯(cuò)誤。

import java.util.concurrent.*;

public class WrapperFutureExceptionDemo {

    public static void main(String[] args) {
        FutureTask<String> originalFutureTask = new FutureTask<>(() -> {
            System.out.println("子線程: 任務(wù)開(kāi)始,即將拋出異常!");
            throw new RuntimeException("計(jì)算出錯(cuò)!");
        });

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<?> wrapperFuture = executor.submit(originalFutureTask);

        try {
            System.out.println("主線程: 調(diào)用 wrapperFuture.get() 等待任務(wù)完成...");
            // 這里會(huì)阻塞,直到任務(wù)結(jié)束。因?yàn)槿蝿?wù)拋了異常,get()也會(huì)拋出異常。
            wrapperFuture.get();
        } catch (InterruptedException e) {
            System.err.println("主線程: 等待時(shí)被中斷了。");
        } catch (ExecutionException e) {
            System.err.println("主線程: 成功捕獲到后臺(tái)任務(wù)的異常!");
            System.err.println("  - 根本原因: " + e.getCause());
        }

        executor.shutdown();
    }
}

運(yùn)行結(jié)果:

子線程: 任務(wù)開(kāi)始,即將拋出異常!
主線程: 調(diào)用 wrapperFuture.get() 等待任務(wù)完成...
主線程: 成功捕獲到后臺(tái)任務(wù)的異常!
  - 根本原因: java.lang.RuntimeException: 計(jì)算出錯(cuò)!

即便不關(guān)心返回值,wrapperFuture.get()在錯(cuò)誤處理和流程同步上也是不可或缺的。

ExecutorService的設(shè)計(jì)哲學(xué)是提供一套統(tǒng)一、可預(yù)測(cè)的接口。

  • submit(Callable<T> task) 返回 Future<T>
  • submit(Runnable task, T result) 返回 Future<T>
  • submit(Runnable task) 返回 Future<?>

無(wú)論你提交什么類(lèi)型的任務(wù),submit方法總是返回一個(gè)Future對(duì)象。這讓使用者可以依賴(lài)一個(gè)統(tǒng)一的模型來(lái)管理所有異步任務(wù),而不必寫(xiě)if-else來(lái)判斷提交的是Callable還是Runnable。wrapperFuture (Future<?>)正是這個(gè)統(tǒng)一模型中,用于代表“無(wú)返回值任務(wù)”的那個(gè)標(biāo)準(zhǔn)占位符。

總結(jié)來(lái)說(shuō),新返回的Future對(duì)象能夠安全、統(tǒng)一地管理后臺(tái)任務(wù),即便并不關(guān)心那個(gè)任務(wù)的返回值。

總結(jié)

到此這篇關(guān)于Java開(kāi)發(fā)異步編程中常用的接口和類(lèi)的文章就介紹到這了,更多相關(guān)Java異步編程接口和類(lèi)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java進(jìn)階之在Word文檔中動(dòng)態(tài)增刪頁(yè)面的完整指南

    Java進(jìn)階之在Word文檔中動(dòng)態(tài)增刪頁(yè)面的完整指南

    本文將介紹如何使用一款第三方 Java 庫(kù),以編程方式實(shí)現(xiàn)對(duì) Word 文檔頁(yè)面及段落內(nèi)容的精細(xì)化管理,文章將從環(huán)境配置開(kāi)始,逐步演示如何新增頁(yè)面,在指定位置插入頁(yè)面以及刪除特定頁(yè)面,希望對(duì)大家有一定的幫助
    2026-05-05
  • Spring boot 使用JdbcTemplate訪問(wèn)數(shù)據(jù)庫(kù)

    Spring boot 使用JdbcTemplate訪問(wèn)數(shù)據(jù)庫(kù)

    SpringBoot 是為了簡(jiǎn)化 Spring 應(yīng)用的創(chuàng)建、運(yùn)行、調(diào)試、部署等一系列問(wèn)題而誕生的產(chǎn)物。本文重點(diǎn)給大家介紹spring boot 使用JdbcTemplate訪問(wèn)數(shù)據(jù)庫(kù),需要的朋友可以參考下
    2018-05-05
  • springboot如何引入外部yml配置文件

    springboot如何引入外部yml配置文件

    這篇文章主要介紹了springboot如何引入外部yml配置文件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • SpringBoot教程_創(chuàng)建第一個(gè)SpringBoot項(xiàng)目

    SpringBoot教程_創(chuàng)建第一個(gè)SpringBoot項(xiàng)目

    這篇文章主要介紹了SpringBoot教程_創(chuàng)建第一個(gè)SpringBoot項(xiàng)目,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Java源碼解析ConcurrentHashMap的初始化

    Java源碼解析ConcurrentHashMap的初始化

    今天小編就為大家分享一篇關(guān)于Java源碼解析ConcurrentHashMap的初始化,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Java Fork/Join框架

    Java Fork/Join框架

    Fork/Join框架是Java7中新增的一項(xiàng)特性,也是Java7平臺(tái)的其中一項(xiàng)主要改進(jìn)。下面我們就來(lái)簡(jiǎn)單探討下Java的Fork/Join框架
    2016-09-09
  • Hibernate5新特性介紹

    Hibernate5新特性介紹

    hibernate5中有了一些新的變動(dòng),下面腳本之家小編把Hibernate5新特性相關(guān)知識(shí),分享到腳本之家平臺(tái),感興趣的朋友參考下吧
    2017-09-09
  • 一次踩坑記錄 @valid注解不生效 排查過(guò)程

    一次踩坑記錄 @valid注解不生效 排查過(guò)程

    這篇文章主要介紹了一次踩坑記錄 @valid注解不生效 排查過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • maven安裝配置的實(shí)現(xiàn)步驟

    maven安裝配置的實(shí)現(xiàn)步驟

    本文主要介紹了maven安裝配置的實(shí)現(xiàn)步驟,包括下載和安裝Maven,配置Maven的環(huán)境變量,以及創(chuàng)建Maven項(xiàng)目,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • Java并發(fā)工具Fork/Join原理

    Java并發(fā)工具Fork/Join原理

    這篇文章主要為大家介紹了Java并發(fā)工具Fork/Join原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06

最新評(píng)論

安远县| 滕州市| 金寨县| 衡东县| 昂仁县| 桐城市| 城步| 富平县| 高陵县| 潍坊市| 同心县| 米易县| 西乌珠穆沁旗| 罗平县| 泰兴市| 夏邑县| 青铜峡市| 宜昌市| 台湾省| 玉环县| 修水县| 新巴尔虎右旗| 白山市| 新乐市| 赞皇县| 南川市| 五莲县| 威远县| 左云县| 苗栗县| 平阴县| 葫芦岛市| 淄博市| 怀远县| 越西县| 衡阳县| 砀山县| 乐亭县| 安宁市| 石泉县| 衡东县|