序言:這篇文章主要記錄了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 執行緒池工作流程
這和執行緒池的工作模式相關。在向執行緒池提交任務時,執行緒池的工作邏輯如下:
- 當 工作執行緒數量 小於 核心執行緒數量 的時候,直接建立新執行緒執行任務。
if (workerCountOf(c) < corePoolSize) { // 工作執行緒數量小於核心執行緒數量 if (addWorker(command, true)){ log.warn("新增核心執行緒" + command); return; } c = ctl.get(); }
- 當 工作執行緒數量 大於等於 核心執行緒數量 的時候,會先把任務提交到佇列中。
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); }
- 當 佇列滿了的時候,會嘗試建立 最大執行緒 執行任務。
else if (!addWorker(command, false)) // 如果新增最大執行緒失敗, 觸發拒絕策略 reject(command); else { log.warn("新增最大執行緒" + command); }
- 如果 佇列滿了,工作執行緒數量也等於最大執行緒數量時,觸發拒絕策略。
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
,
當執行緒池從佇列獲取元素時, 執行的是 poll
和 take
方法,其中 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.");
}
}