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

Java 多線程實戰:繼承 Thread 類實現年份日誌並發分析

     編輯:Java 相關知識

前言

繼承 Thread 類是 Java 多線程編程的入門方式。本文通過一個「年份點擊日誌並發統計」的實戰案例,展示如何拆分任務、啟動線程、等待完成並聚合結果,同時指出代碼中存在的同步與效率問題。

一、業務場景

需求:統計某一年每月的文章點擊量。日誌文件按天存儲(如 article.click.2016-05-20.txt),需解析全年 12 個月、每月 28-31 天的文件,統計行數作為點擊量。

二、核心代碼結構

2.1 控制器:任務分發與結果聚合

public void yearlyClickThread() {
    String year = getPara("year");
    List<String> xList = Arrays.asList("January",..., "December");
    List<Integer> yList = new ArrayList<>();
    List<Thread> threadList = new ArrayList<>();

    for (int m = 1; m <= 12; m++) {
        // 1. 構建當月所有日期
        List<String> dateList = buildDates(year, m);
        // 2. 創建並啟動線程
        Thread thread = new ReadLogFileThreadByYear(dateList);
        thread.start();
        // 3. 簡陋的節流:睡眠 1 秒
        Thread.sleep(1000L);
        threadList.add(thread);
    }

    // 4. 輪詢檢查線程狀態
    int terminated = 0;
    for (Thread t : threadList) {
        if (t.getState() == Thread.State.TERMINATED) terminated++;
    }
    if (terminated == 12) { // 全部結束
        // 5. 從靜態共享列表讀取結果
        List<Map<String, Object>> list = ReadLogFileThreadByYear.list;
        // 聚合邏輯...
        renderJson();
    }
}

2.2 線程類:文件解析與共享寫入

public class ReadLogFileThreadByYear extends Thread {
    private List<String> clickDate;
    // 靜態共享列表 —— 所有線程並發寫入,無同步!
    public static List<Map<String, Object>> list = new ArrayList<>();

    public void run() {
        Map<String, Object> map = new HashMap<>();
        int monthClick = 0;
        for (String date : clickDate) {
            File file = new File(LOG_LOCATION, "article.click." + date + ".txt");
            if (!file.exists()) continue;
            // 逐行讀取計數...
            monthClick += countLines(file);
        }
        map.put("month", dateList.get(0).substring(5,7));
        map.put("clickCount", monthClick);
        list.add(map); // 非線程安全操作
    }
}

三、代碼問題深度剖析

問題風險改進方案
靜態 List 並發 add 無同步數據丟失、下標越界、內部數組擴容異常用 Collections.synchronizedList、CopyOnWriteArrayList 或線程局部存儲最後匯總
Thread.sleep(1000) 節流啟動人為延長總耗時,無法保證線程真正啟動完成用 CountDownLatch/CyclicBarrier 或線程池 invokeAll
輪詢 Thread.getState() 判斷完成狀態可變、不可靠,主線程可能過早/過晚讀取結果thread.join() 阻塞等待,或 Future.get()
繼承 Thread 而非實現 Runnable/Callable職責不清,難以復用、難以接入線程池實現 Callable<Map>,配合 ExecutorService
IO 密集型任務無線程池限制12 個月並發 12 線程尚可,若擴展到天級並發數百線程會耗盡資源固定大小線程池 Executors.newFixedThreadPool(IO 核心數*2)
異常吞沒catch (Exception e) { e.printStackTrace(); } 導致主線程感知不到失敗拋出受檢異常或返回 Future 統一處理

四、重構版:基於 ExecutorService + Callable

public void yearlyClickThreadModern() throws Exception {
    String year = getPara("year");
    ExecutorService pool = Executors.newFixedThreadPool(8); // IO 密集型適當放大
    List<Future<MonthResult>> futures = new ArrayList<>();

    for (int m = 1; m <= 12; m++) {
        final int month = m;
        futures.add(pool.submit(() -> {
            List<String> dates = buildDates(year, month);
            int total = 0;
            for (String d : dates) {
                File f = new File(LOG_LOCATION, "article.click." + d + ".txt");
                if (f.exists()) total += countLines(f);
            }
            return new MonthResult(month, total);
        }));
    }

    List<Integer> yList = new ArrayList<>(Collections.nCopies(12, 0));
    int totalCount = 0;
    for (Future<MonthResult> f : futures) {
        MonthResult r = f.get(); // 阻塞等待結果,自動傳播異常
        yList.set(r.month - 1, r.count);
        totalCount += r.count;
    }
    pool.shutdown();
    renderJson(totalCount, yList);
}

record MonthResult(int month, int count) {}

五、總結

  • 繼承 Thread 適合教學演示,生產環境**首選 Runnable/Callable + 線程池**。
  • 共享可變狀態是多線程 Bug 溫床,**優先無共享、次選線程安全容器、最後才用鎖**。
  • 等待結果用 Future/CompletionService,不要輪詢 Thread.getState()。
  • IO 密集型任務線程池大小可設為 CPU 核心數 * 2 或更大,配合非阻塞 IO (NIO/Netty) 效果更佳。

從「能跑通」到「健壯高效」,多線程編程的核心是**任務分解、狀態隔離、同步協調**三板斧。

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