首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程編程:從線程獲取返回結果的兩種標準方式

     編輯:Java 相關知識

核心矛盾

線程 start() 立即返回,run() 異步執行。主線程若直接讀取線程字段,大概率遇到 可見性問題(讀到 null/舊值)或 時序問題(線程尚未寫入)。

方式一:共享變量 + 同步等待(傳統模式)

錯誤示範

MyThread t = new MyThread();
t.start();
System.out.println(t.result); // 極大概率印 null

修正方案:join() 同步屏障

public class JoinDemo extends Thread {
    private String result;

    @Override
    public void run() {
        // 模擬耗時計算
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        result = "計算完成: " + System.currentTimeMillis();
    }

    public static void main(String[] args) throws InterruptedException {
        JoinDemo t = new JoinDemo();
        t.start();
        t.join(); // 關鍵:阻塞主線程,直到 t 終止
        // join() 返回時,JMM 保證 t.run() 所有寫入對主線程可見
        System.out.println(t.result); // 必見真實結果
    }
}

原理:Thread.join() 底層調用 wait(),線程終止前調用 notifyAll()。JMM 保證:線程終止前的所有寫入,對成功從 join() 返回的線程可見(happens-before 規則)。

輪詢替代方案(不推薦)

// 低效且不保證可見性(無同步動作)
while (t.result == null) { /* busy spin */ }

// 改良:睡眠輪詢 (仍不保證可見性,除非 result 為 volatile)
while (t.result == null) { Thread.sleep(100); }

結論:永遠優先使用 join(),簡潔、正確、高效(無忙等)。

方式二:回調函數 / 觀察者模式(異步非阻塞)

當主線程不能/不願阻塞時,將「結果處理邏輯」作為回調傳入線程:


// 回調接口
interface ResultHandler {
    void onResult(String result);
    void onError(Throwable t);
}

// 線程實現
public class CallbackThread extends Thread {
    private final ResultHandler handler;

    public CallbackThread(ResultHandler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        try {
            String result = doHeavyWork();
            handler.onResult(result);
        } catch (Throwable e) {
            handler.onError(e);
        }
    }
}

// 使用:主線程繼續幹別的事
new CallbackThread(new ResultHandler() {
    @Override public void onResult(String r) { 
        System.out.println("異步收到結果: " + r); 
        updateUI(r); // 如 Swing/JavaFX 需在 EDT 執行
    }
    @Override public void onError(Throwable t) { log.error("失敗", t); }
}).start();

System.out.println("主線程未阻塞,繼續執行...");

現代替代方案(Java 8+ 強烈推薦)

API適用場景核心優勢
Future<T> / Callable<T>需要阻塞獲取結果、可取消標準化、支援超時、異常傳播
CompletableFuture<T>異步鏈式處理、多任務編排非阻塞、函數式組合、異常處理豐富
ExecutorService.submit()線程池管理、資源復用生產級、可監控、拒絕策略
CompletableFuture.supplyAsync()簡單異步計算一行代碼、默認公共池

CompletableFuture 範例

// 異步計算 + 異步回調 + 異常處理
CompletableFuture.supplyAsync(() -> {
    Thread.sleep(1000);
    return "異步結果";
}).thenAccept(result -> {
    System.out.println("收到: " + result);
}).exceptionally(ex -> {
    log.error("異步失敗", ex);
    return null;
});

// 主線程繼續...

總結建議

  1. 簡單阻塞等待:Thread.join() + 共享字段(字段建議 volatile)
  2. 生產級任務提交:ExecutorService.submit(Callable) 返回 Future
  3. 複雜異步流程:CompletableFuture 鏈式編程
  4. 避免:手工 wait/notify、忙等輪詢、非 volatile 共享字段

掌握 happens-before 原則 與 高級併發 API,是寫出正確、高性能並發代碼的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved