首页 > 其他分享 >深入理解线程池的运行流程

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

时间:2024-09-08 17:46:57浏览次数:3  
标签:code threadDemo 流程 09 2024 深入 线程 08 MyBlockQueue

序言:这篇文章主要记录了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.");
    }
}

标签:code,threadDemo,流程,09,2024,深入,线程,08,MyBlockQueue
From: https://www.cnblogs.com/ZGByoyo/p/18403178

相关文章

  • 数据访问连接池和线程池
       数据传输的过程首先要建立网络连接 。数据传输单元为数据包 DATA PRAGRAM. 计算机数 据网络的互通互联物理硬件和软件程序的管理。局域网络是美国国防部连接不同计算机器设 备的一种方式 。光缆传输数据的速度更慢 。海底光纤的架设, 2000年左右使得全球互联网......
  • MES系统:优化生产流程,产品质量与追溯性
    MES系统(ManufacturingExecutionSystem,制造执行系统)在实现产品质量管理方面发挥着重要作用。它通过集成生产过程的各种数据和功能,帮助企业提升产品质量、降低不良率,并确保生产过程的稳定性和一致性。以下是万界星空科技MES系统实现产品质量管理的几个关键方面:一、数据采集与......
  • velero备份与恢复流程是什么
    Velero是一个用于在Kubernetes集群中备份和恢复资源及持久数据的工具。它能够帮助用户保护应用程序及其数据,以防止数据丢失或集群故障。以下是Velero的备份与恢复流程:1.备份流程a.安装Velero在使用Velero之前,首先需要安装Velero。可以通过Helm或者使用VeleroCLI......
  • 深入FastAPI:掌握使用多个关联模型的高级用法
    在构建RESTfulAPI时,经常需要处理复杂的数据关系。FastAPI通过支持多个关联模型,使得定义这些关系变得简单直观。这种方法不仅提高了代码的可维护性,还增强了API的灵活性。通过使用Pydantic库,我们可以轻松定义数据模型及其关联,从而在FastAPI应用中实现强大的数据处理逻辑。无论是一对......
  • 深入浅出:什么是日志技术?如何在项目中使用?
    深入浅出:什么是日志技术?如何在项目中使用?在软件开发中,日志技术就像一本记录系统“点点滴滴”的日记。系统运行时,它记录下关键事件——正常操作、异常情况都不会遗漏。日志不仅是开发人员分析系统行为的利器,还可以在项目出现问题时,帮助我们快速找到问题根源。那么,如何让系统“记日......
  • Emitter 线程通信
    文章目录前言一、线程通信二、Emitter通信1.介绍2.使用方法3.使用场景总结前言线程是一个操作系统能够运算调度的最小单位,被包含在进程之中,也是进程中的实际运作单位。而在一个应用中也许会有很多个进程,但是所有的UIAbility、ServiceExtensionAbility和Data......
  • 深入掌握Go语言中的正则表达式与字符串处理
    Go语言中的正则表达式与模式匹配在编程中,字符串处理是常见的需求之一,而正则表达式则是一个强大的工具,能够帮助我们实现复杂的字符串匹配、提取和替换功能。Go语言内置了对正则表达式的支持,通过regexp包,我们可以轻松实现模式匹配的各种操作。本文将详细介绍正则表达式在Go语......
  • C# 多线程的学习大纲
    C#多线程编程是开发高效并发应用的核心技术之一。以下是一个详细的学习大纲,涵盖了C#多线程编程的各个方面,从基础概念到高级主题。学习大纲1.多线程基础知识1.1什么是线程?定义线程及其在操作系统中的角色进程与线程的区别1.2C#中的多线程基础Thread类的基本使......
  • Java SE面试题 ——深入理解
           目录        1.什么是JDK,JRE,JVM;他们之间有什么联系    2.Java的八种数据类型 3.&和&&区别                                          4.switch的参数可以有哪些类型 5.类与对象如......
  • 阅读周·深入浅出的Node.js | 异步I/O、事件驱动和单线程,Node基调三元素了解一下
    背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效。已读完书籍:《架构简洁之道》。当前阅读......