深入理解线程池的运行流程
序言:这篇文章主要记录了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.");
}
}