深入理解執行緒池的執行流程

zzzggb發表於2024-09-08

序言:這篇文章主要記錄了java執行緒池在一些特殊場景出現的奇怪問題。

場景

核心執行緒數量為2,最大執行緒數量為4,生存時間60s,任務佇列大小為4。每次向執行緒池中提交8個任務執行。那麼,這個執行緒池能否正常執行呢?

1 demo

我們可以根據這個要求寫一個demo出來

public class Demo {
    static int coreSize = 2;
    static int maxSize = 4;
    static int queueSize = 4;
    
    // 這裡的 MyExecutor, MyBlockQueue 是複製的 Executor 和 BlockQueue , 方便新增日誌
    static MyExecutor executor = new MyExecutor(coreSize, maxSize, 60, TimeUnit.SECONDS,
            new MyBlockQueue<>(queueSize), new NamingThreadFactory("thread"));

    public static void main(String[] args) throws InterruptedException {
        int cnt = maxSize + queueSize;  // 每次提交的任務數量
        CountDownLatch latch = new CountDownLatch(cnt);
        int T = 2;  // 任務執行週期次數
        for (int t = 0; t < T; t++) {
            for (int i = 0; i < cnt; i++) {
                executor.execute(new Task(i, t, latch));
            }
            latch.await();
        }
        executor.shutdown();
    }

    static class Task implements Runnable {
        int id;
        int batch;
        CountDownLatch latch;

        Task(int id, int batch, CountDownLatch latch) {
            this.id = id;
            this.batch = batch;
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(100);
                latch.countDown();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String toString() {
            return " Task [" + "batch= " + batch + ", id=" + id + "]";
        }
    }
}

在這個程式碼流程中,我們每次提交8個任務到執行緒池執行, 等待上一批執行完成之後再提交新的任務。理論上來說執行緒池是應該可以處理8個任務的。

但是這個程式碼在第二批任務的第5個任務會穩定的觸發拒絕策略。準確的說,是在第queueSize + 1個任務時會觸發拒絕策略。

2 執行緒池工作流程

這和執行緒池的工作模式相關。在向執行緒池提交任務時,執行緒池的工作邏輯如下:

  1. 當 工作執行緒數量 小於 核心執行緒數量 的時候,直接建立新執行緒執行任務。
    if (workerCountOf(c) < corePoolSize) {      // 工作執行緒數量小於核心執行緒數量
        if (addWorker(command, true)){
            log.warn("新增核心執行緒" + command);
            return;
        }
        c = ctl.get();
    }
    
  2. 當 工作執行緒數量 大於等於 核心執行緒數量 的時候,會先把任務提交到佇列中。
    if (isRunning(c) && workQueue.offer(command)) {     // 執行緒池處於執行狀態並且佇列未滿
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command))     // 因為上一個if不是原子操作,再次檢查執行緒池狀態
            reject(command);
        else if (workerCountOf(recheck) == 0)       // 確保執行緒池中有可使用的工作執行緒
            addWorker(null, false);
    }
    
  3. 當 佇列滿了的時候,會嘗試建立 最大執行緒 執行任務。
      else if (!addWorker(command, false))   // 如果新增最大執行緒失敗, 觸發拒絕策略
          reject(command);
      else {
          log.warn("新增最大執行緒" + command);
      }
    
  4. 如果 佇列滿了,工作執行緒數量也等於最大執行緒數量時,觸發拒絕策略。

3 發現問題

回到剛剛的問題, 在第一次提交8個任務時,執行緒池的狀態變化為:

[2024-09-08 16:06:08.616] - [WARN ] - [main           ] - [code.threadDemo.MyExecutor    ] : 新增核心執行緒 Task [batch= 0, id=0]
[2024-09-08 16:06:08.629] - [WARN ] - [main           ] - [code.threadDemo.MyExecutor    ] : 新增核心執行緒 Task [batch= 0, id=1]
[2024-09-08 16:06:08.629] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 0, id=2]
[2024-09-08 16:06:08.630] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 0, id=3]
[2024-09-08 16:06:08.630] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 0, id=4]
[2024-09-08 16:06:08.630] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 0, id=5]
[2024-09-08 16:06:08.630] - [INFO1] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿 Task [batch= 0, id=6]
[2024-09-08 16:06:08.630] - [WARN ] - [main           ] - [code.threadDemo.MyExecutor    ] : 新增最大執行緒 Task [batch= 0, id=6]
[2024-09-08 16:06:08.631] - [INFO1] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿 Task [batch= 0, id=7]
[2024-09-08 16:06:08.631] - [WARN ] - [main           ] - [code.threadDemo.MyExecutor    ] : 新增最大執行緒 Task [batch= 0, id=7]
[2024-09-08 16:06:08.689] - [INFO ] - [thread - 1     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=0]
[2024-09-08 16:06:08.689] - [INFO ] - [thread - 1     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 0, id=2]
[2024-09-08 16:06:08.737] - [INFO ] - [thread - 2     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=1]
[2024-09-08 16:06:08.737] - [INFO ] - [thread - 4     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=7]
[2024-09-08 16:06:08.737] - [INFO ] - [thread - 3     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=6]
[2024-09-08 16:06:08.737] - [INFO ] - [thread - 2     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 0, id=3]
[2024-09-08 16:06:08.737] - [INFO ] - [thread - 4     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 0, id=4]
[2024-09-08 16:06:08.738] - [INFO ] - [thread - 3     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 0, id=5]
[2024-09-08 16:06:08.800] - [INFO ] - [thread - 1     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=2]
[2024-09-08 16:06:08.800] - [INFO ] - [thread - 1     ] - [code.threadDemo.MyBlockQueue  ] : poll 等待任務
[2024-09-08 16:06:08.847] - [INFO ] - [thread - 2     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=3]
[2024-09-08 16:06:08.847] - [INFO ] - [thread - 4     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=4]
[2024-09-08 16:06:08.847] - [INFO ] - [thread - 3     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 0, id=5]

到此,執行緒池的最大執行緒已經全部啟動。當第一批任務執行完成,第二批任務開始時,執行緒池狀態變化為:

[2024-09-08 16:06:08.847] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 1, id=0]
[2024-09-08 16:06:08.847] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 1, id=1]
[2024-09-08 16:06:08.848] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 1, id=2]
[2024-09-08 16:06:08.848] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 1, id=3]
[2024-09-08 16:06:08.848] - [INFO1] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿 Task [batch= 1, id=4]
[2024-09-08 16:06:08.848] - [INFO ] - [thread - 2     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 1, id=0]
[2024-09-08 16:06:08.848] - [ERROR] - [main           ] - [code.threadDemo.MyExecutor    ] : 拒絕任務  Task [batch= 1, id=4]
[2024-09-08 16:06:08.848] - [INFO ] - [thread - 4     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 1, id=1]
[2024-09-08 16:06:08.849] - [INFO ] - [thread - 3     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 1, id=2]
[2024-09-08 16:06:08.849] - [WARN ] - [thread - 1     ] - [code.threadDemo.MyBlockQueue  ] : poll 被喚醒
[2024-09-08 16:06:08.849] - [INFO ] - [thread - 1     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 1, id=3]
[2024-09-08 16:06:08.849] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 1, id=5]
[2024-09-08 16:06:08.849] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 1, id=6]
[2024-09-08 16:06:08.850] - [DEBUG] - [main           ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列 Task [batch= 1, id=7]
[2024-09-08 16:06:08.959] - [INFO ] - [thread - 2     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 1, id=0]
[2024-09-08 16:06:08.959] - [INFO ] - [thread - 4     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 1, id=1]
[2024-09-08 16:06:08.959] - [INFO ] - [thread - 3     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 1, id=2]
[2024-09-08 16:06:08.959] - [INFO ] - [thread - 1     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 1, id=3]
[2024-09-08 16:06:08.959] - [INFO ] - [thread - 2     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 1, id=5]
[2024-09-08 16:06:08.959] - [INFO ] - [thread - 4     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 1, id=6]
[2024-09-08 16:06:08.959] - [INFO ] - [thread - 3     ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務 Task [batch= 1, id=7]
[2024-09-08 16:06:09.071] - [INFO ] - [thread - 2     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 1, id=5]
[2024-09-08 16:06:09.071] - [INFO ] - [thread - 4     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 1, id=6]
[2024-09-08 16:06:09.071] - [INFO ] - [thread - 3     ] - [code.threadDemo.MyExecutor    ] : 任務執行完畢  Task [batch= 1, id=7]

可以看到在提交第五個任務發生了佇列滿的事件,然後由於當前執行緒池的工作執行緒數量已經是最大執行緒數量了,所以觸發了拒絕策略。

但是執行緒池中有明明四個執行緒在等待任務執行,為什麼main執行緒一直放而沒有執行緒來消費呢?

4 尋找原因

Note : 以下內容是個人想法,不一定準確

這裡的任務佇列是使用的 ArrayBlockingQueue,
當執行緒池從佇列獲取元素時, 執行的是 polltake 方法,其中 poll 是帶超時時間的, take是無限等待的。這裡主要看poll方法:

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
     long nanos = unit.toNanos(timeout);
     final ReentrantLock lock = this.lock;
     lock.lockInterruptibly();
     try {
         while (count == 0) {
             if (nanos <= 0L) {
                 log.warn("poll 等待超時");
                 return null;
             }
             log.info("poll 等待任務");
             nanos = notEmpty.awaitNanos(nanos);
             log.warn("poll 被喚醒");
         }
         E e = dequeue();
         log.info("poll 獲取任務" + e);
         return e;
     } finally {
         lock.unlock();
     }
}

可以看到當佇列元素數量為 0 時,執行緒會呼叫 notEmpty.awaitNanos(nanos) 進入等待,直到被喚醒或者超時。
當向佇列提交任務時,執行的是offer(E e)方法:

public boolean offer(E e) {
     Objects.requireNonNull(e);
     final ReentrantLock lock = this.lock;
     lock.lock();
     try {
         if (count == items.length){
             log.info1("任務佇列已滿" + e);
             return false;
         }
         else {
             log.debug("放入任務佇列" + e);
             enqueue(e);
             return true;
         }
     } finally {
         lock.unlock();
     }
}

private void enqueue(E e) {
   final Object[] items = this.items;
        items[putIndex] = e;
        if (++putIndex == items.length) putIndex = 0;
        count++;
        notEmpty.signal();
    }

在 offer 方法可以看到在有元素入隊的時候會觸發 notEmpty.signal() 喚醒正在等待的執行緒。 但是在我們的測試情況中消費執行緒並沒有立刻消費佇列中任務,而main執行緒在一直在放入。

這裡我感覺和執行緒的排程有關係,當消費執行緒被喚醒時,只是從阻塞轉為了就緒而已,但實際的執行還需要等待cpu排程。而main執行緒一直處於執行狀態,所以可以一直向佇列放入任務。

為此我還測試了一下在 ArrayBlockingQueue 中生產者和消費者的供銷關係:

public static void main(String[] args) throws InterruptedException {
     MyBlockQueue<Integer> queue = new MyBlockQueue<>(90);
   
     Thread t1 = new Thread(() -> {
         for (int i = 0; i < 100; i++) {
             queue.offer(i);
         }
     });
   
     Thread t2 = new Thread(() -> {
         for (int i = 0; i < 100; i++) {
             try {
                 queue.poll(i, TimeUnit.SECONDS);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
     });
     t2.start();
     t1.start();
   
     t1.join();
     t2.join();
}

這個程式碼的結果為:

[2024-09-08 17:19:43.583] - [WARN ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 等待超時
[2024-09-08 17:19:43.601] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 等待任務
[2024-09-08 17:19:43.602] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列0
[2024-09-08 17:19:43.602] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列1
[2024-09-08 17:19:43.603] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列2
[2024-09-08 17:19:43.603] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列3
[2024-09-08 17:19:43.603] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列4
[2024-09-08 17:19:43.603] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列5
[2024-09-08 17:19:43.603] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列6
[2024-09-08 17:19:43.604] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列7
[2024-09-08 17:19:43.604] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列8
[2024-09-08 17:19:43.604] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列9
[2024-09-08 17:19:43.604] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列10
[2024-09-08 17:19:43.604] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列11
[2024-09-08 17:19:43.604] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列12
[2024-09-08 17:19:43.605] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列13
[2024-09-08 17:19:43.605] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列14
[2024-09-08 17:19:43.605] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列15
[2024-09-08 17:19:43.605] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列16
[2024-09-08 17:19:43.605] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列17
[2024-09-08 17:19:43.606] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列18
[2024-09-08 17:19:43.606] - [DEBUG] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 放入任務佇列19
[2024-09-08 17:19:43.606] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿20
[2024-09-08 17:19:43.606] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿21
[2024-09-08 17:19:43.606] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿22
[2024-09-08 17:19:43.607] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿23
[2024-09-08 17:19:43.607] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿24
[2024-09-08 17:19:43.607] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿25
[2024-09-08 17:19:43.607] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿26
[2024-09-08 17:19:43.608] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿27
[2024-09-08 17:19:43.608] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿28
[2024-09-08 17:19:43.608] - [INFO1] - [Thread-0       ] - [code.threadDemo.MyBlockQueue  ] : 任務佇列已滿29
[2024-09-08 17:19:43.608] - [WARN ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 被喚醒
[2024-09-08 17:19:43.609] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務0
[2024-09-08 17:19:43.609] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務1
[2024-09-08 17:19:43.609] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務2
[2024-09-08 17:19:43.609] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務3
[2024-09-08 17:19:43.609] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務4
[2024-09-08 17:19:43.610] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務5
[2024-09-08 17:19:43.610] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務6
[2024-09-08 17:19:43.610] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務7
[2024-09-08 17:19:43.610] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務8
[2024-09-08 17:19:43.610] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務9
[2024-09-08 17:19:43.610] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務10
[2024-09-08 17:19:43.611] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務11
[2024-09-08 17:19:43.611] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務12
[2024-09-08 17:19:43.611] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務13
[2024-09-08 17:19:43.611] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務14
[2024-09-08 17:19:43.611] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務15
[2024-09-08 17:19:43.611] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務16
[2024-09-08 17:19:43.611] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務17
[2024-09-08 17:19:43.612] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務18
[2024-09-08 17:19:43.612] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 獲取任務19
[2024-09-08 17:19:43.612] - [INFO ] - [Thread-1       ] - [code.threadDemo.MyBlockQueue  ] : poll 等待任務

可以發現在任務數量少的時候 放任務取任務 的動作基本是分開的。在任務數量大的時候也是分批次交替的。

線上程池中應該也是這樣,放入任務喚醒等待的消費執行緒也並不意味著消費執行緒就能立刻消費任務。

遺留問題

在最開始的Demo中還有一個穩定發生的奇怪事情,在佇列的容量較小時(大概100以內),當佇列第一次滿後消費執行緒就能開始工作了。

而在ArrayBlockingQueue的測試中,即使佇列滿了消費執行緒也不能一定開始工作。

最後

最後給大家留一個平常寫程式碼的 log 工具類,至於為什麼不直接使用 Slf4j 呢,當然是它改起來太麻煩了

public class Logger {

    public enum LogLevel {
        DEBUG, INFO, INFO1, WARN, ERROR
    }

    private final String className;
    private final LogLevel level;

    public Logger(LogLevel level, Class<?> clazz) {
        this.level = level;
        this.className = clazz.getName();
    }

    private static final String RESET = "\u001B[0m";
    private static final String DEBUG_COLOR = "\u001B[34m"; // Blue
    private static final String INFO_COLOR = "\u001B[32m";  // Green
    private static final String INFO1_COLOR = "\u001B[35m"; // Red
    private static final String WARN_COLOR = "\u001B[33m";  // Yellow
    private static final String ERROR_COLOR = "\u001B[31m"; // Red

    private static final int LEVEL_WIDTH = 5;
    private static final int THREAD_NAME_WIDTH = 15;
    private static final int CLASS_NAME_WIDTH = 30;

    public void log(LogLevel level, String message) {
        if (this.level.ordinal() <= level.ordinal()) {
            String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
            String threadName = Thread.currentThread().getName();
            String color = getColor(level);
            String formattedLevel = formatString(level.name(), LEVEL_WIDTH);
            String formattedThreadName = formatString(threadName, THREAD_NAME_WIDTH);
            String formattedClassName = formatString(className, CLASS_NAME_WIDTH);
            // 格式化日誌輸出
            System.out.println(
                    color + "[" + timestamp + "] - [" + formattedLevel + "] - [" + formattedThreadName + "] - [" + formattedClassName + "] : " + message + RESET
            );
        }
    }

    private String formatString(String str, int width) {
        if (str.length() > width) {
            return str.substring(0, width);
        } else {
            return String.format("%-" + width + "s", str);
        }
    }

    private String getColor(LogLevel level) {
        return switch (level) {
            case DEBUG -> DEBUG_COLOR;
            case INFO -> INFO_COLOR;
            case WARN -> WARN_COLOR;
            case ERROR -> ERROR_COLOR;
            case INFO1 -> INFO1_COLOR;
        };
    }

    public void debug(String message) {
        log(LogLevel.DEBUG, message);
    }

    public void info(String message) {
        log(LogLevel.INFO, message);
    }

    public void info1(String message) {
        log(LogLevel.INFO1, message);
    }

    public void warn(String message) {
        log(LogLevel.WARN, message);
    }

    public void error(String message) {
        log(LogLevel.ERROR, message);
    }

    public static void main(String[] args) {
        Logger logger = new Logger(LogLevel.DEBUG, Logger.class);
        logger.debug("This is a debug message.");
        logger.info("This is an info message.");
        logger.warn("This is a warning message.");
        logger.error("This is an error message.");
    }
}

相關文章