深入理解线程池的运行流程

序言:这篇文章主要记录了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.");
    }
}
posted @ 2024-09-08 17:48  zzzggb  阅读(34)  评论(0编辑  收藏  举报