【Java 线程池】线程池的源码应用

1  前言

我们平时异步会用到线程池,那你怎么用的呢?用的对不对呢?这节我们看看源码中的一些应用场景,看看他们是如何设置参数,如何停止的。

回忆一下线程池的几个参数:核心线程数、最大线程数、空闲时间、队列、线程工厂、拒绝策略。

2  源码应用

我们就看看他们是如何创建线程池、线程池的参数设置、线程的停止等几个角度看起。

2.1  Tomcat 里的 EndPoint 类

我们平时 SpringBoot 会配置如下参数,他们其实就是设置线程池的。

# 设置最小线程数
server.tomcat.min-spare-threads=50
# 设置最大线程数
server.tomcat.max-threads=200

2.1.1  线程池的创建以及参数设置

直接看 AbstractEndpoint:

public void createExecutor() {
    internalExecutor = true;
    // 哇,虚拟线程,这个没用过,我们这里不看
    if (getUseVirtualThreads()) {
        executor = new VirtualThreadExecutor(getName() + "-virt-");
    } else {
        // 参数队列
        TaskQueue taskqueue = new TaskQueue();
        // 线程工厂
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
        // 创建线程 空闲时间60s 拒绝策略采取默认拒绝抛异常的策略
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }
}
// 线程池的核心线程数,取最大最小的小者
public int getMinSpareThreads() {
    return Math.min(getMinSpareThreadsInternal(), getMaxThreads());
}
// 最大线程数
public int getMaxThreads() {
    if (internalExecutor) {
        return maxThreads;
    } else {
        return -1;
    }
}

可以看到:核心线程数取的 minSpareThreads 和 maxThreads 的最小者,最大线程数取的 maxThreads 默认200,队列用的是 TaskQueue,线程工厂用的是 TaskThreadFactory,空闲时间60s,拒绝策略用的默认的抛异常的那个策略。

那我们看看它的队列:TaskQueue taskqueue = new TaskQueue();

public class TaskQueue extends LinkedBlockingQueue<Runnable> {
    private static final long serialVersionUID = 1L;
    protected static final StringManager sm = StringManager.getManager(TaskQueue.class);
    private transient volatile ThreadPoolExecutor parent = null;
    public TaskQueue() {
        super();
    }
}
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    private static final long serialVersionUID = -6903933977591709194L;

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    ...  
}

可以看到它用的实际上是:LinkedBlockingQueue,队列大小默认 Integer 的最大值哈。

那我们这里顺便说个别的,我们会设置等待队列的大小,怎么没看到是设置队列的大小呢?

# 设置等待队列的最大容量为200
server.tomcat.max-connections=200

其实人家把这个前置了,交给了 LimitLatch ,内部有个 AQS 来控制的哈。

public void setMaxConnections(int maxCon) {
    this.maxConnections = maxCon;
    LimitLatch latch = this.connectionLimitLatch;
    if (latch != null) {
        // Update the latch that enforces this
        if (maxCon == -1) {
            releaseConnectionLatch();
        } else {
            latch.setLimit(maxCon);
        }
    } else if (maxCon > 0) {
        initializeConnectionLatch();
    }
}

我们继续看下它的线程工厂:TaskThreadFactory

public class TaskThreadFactory implements ThreadFactory {

    private final ThreadGroup group;
    // 内部编号器
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    // 线程名称前缀-业务相关
    private final String namePrefix;
    // 是否守护线程
    private final boolean daemon;
    // 调度优先级
    private final int threadPriority;

    public TaskThreadFactory(String namePrefix, boolean daemon, int priority) {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
        this.namePrefix = namePrefix;
        this.daemon = daemon;
        this.threadPriority = priority;
    }

    @Override
    public Thread newThread(Runnable r) {
        TaskThread t = new TaskThread(group, r, namePrefix + threadNumber.getAndIncrement());
        t.setDaemon(daemon);
        t.setPriority(threadPriority);

        if (Constants.IS_SECURITY_ENABLED) {
            // Set the context class loader of newly created threads to be the
            // class loader that loaded this factory. This avoids retaining
            // references to web application class loaders and similar.
            PrivilegedAction<Void> pa = new PrivilegedSetTccl(
                    t, getClass().getClassLoader());
            AccessController.doPrivileged(pa);

            // This method may be triggered from an InnocuousThread. Ensure that
            // the thread inherits an appropriate AccessControlContext
            pa = new PrivilegedSetAccessControlContext(t);
            AccessController.doPrivileged(pa);
        } else {
            t.setContextClassLoader(getClass().getClassLoader());
        }

        return t;
    }
}

线程又用 TaskThread 包装了一层,内部主要多加了一个创建时间。

public class TaskThread extends Thread {

    private static final Log log = LogFactory.getLog(TaskThread.class);
    private final long creationTime;

    public TaskThread(ThreadGroup group, Runnable target, String name) {
        super(group, new WrappingRunnable(target), name);
        this.creationTime = System.currentTimeMillis();
    }

    public TaskThread(ThreadGroup group, Runnable target, String name,
            long stackSize) {
        super(group, new WrappingRunnable(target), name, stackSize);
        this.creationTime = System.currentTimeMillis();
    }
    ...
}

2.1.2  线程池的停止

看完创建,我们继续看看停止:

// 线程池结束的等待时间
private long executorTerminationTimeoutMillis = 5000;
public long getExecutorTerminationTimeoutMillis() {
    return executorTerminationTimeoutMillis;
}
public void shutdownExecutor() {
    Executor executor = this.executor;
    if (executor != null && internalExecutor) {
        this.executor = null;
        if (executor instanceof ThreadPoolExecutor) {
            //this is our internal one, so we need to shut it down
            ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
            // 直接调用 shutdownNow() 不再接收新的  正在运行的和在队列呆着的都中断掉
            tpe.shutdownNow();
            long timeout = getExecutorTerminationTimeoutMillis();
            if (timeout > 0) {
                try {
                    // 等待线程池结束 默认5s
                    tpe.awaitTermination(timeout, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    // Ignore
                }
                // 打印
                if (tpe.isTerminating()) {
                    getLog().warn(sm.getString("endpoint.warn.executorShutdown", getName()));
                }
            }
            // 释放引用
            TaskQueue queue = (TaskQueue) tpe.getQueue();
            queue.setParent(null);
        }
    }
}

可以看到,直接调用线程池的 shutdownNow()哈,并且默认会等待 5s哈。

2.2  Apollo 里的 ReleaseMessageScanner

Apollo 当你在门户发布一条配置变更时,那下游的客户端怎么知道的呢?其实是 Service 服务有个调度的线程池每隔 1s 去捞一下数据库里的变更信息(类似基于数据库的生产消费),发现有新的配置变更后,便通知下游的客户端,去获取新的配置的。

2.2.1  线程池的创建以及参数设置

public class ReleaseMessageScanner implements InitializingBean {
  private static final Logger logger = LoggerFactory.getLogger(ReleaseMessageScanner.class);
  ...
  private long maxIdScanned;

  public ReleaseMessageScanner() {
    listeners = Lists.newCopyOnWriteArrayList();
    // 创建调度线程池 核心线程数=1  线程工厂 ApolloThreadFactory
    executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory
        .create("ReleaseMessageScanner", true));
    missingReleaseMessages = Maps.newHashMap();
  }
}
// Executors 类
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
// ScheduledThreadPoolExecutor 类
// 最大线程数 Integer最大值,空闲时间0 队列用的延时队列
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory);
}

我们继续看看调度任务:

// 默认扫描间隔时间 1000ms 也就是 1s
private static final int DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS = 1000;
public int releaseMessageScanIntervalInMilli() {
  int interval = getIntProperty("apollo.message-scan.interval", DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS);
  return checkInt(interval, 100, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS);
}

@Override
public void afterPropertiesSet() throws Exception {
  databaseScanInterval = bizConfig.releaseMessageScanIntervalInMilli();
  maxIdScanned = loadLargestMessageId();
  // 固定时间间隔的方式运行  这里顺便带一嘴和频率的差异  比如每隔5s跑一次 当实际执行时间小于5s的话,那它俩效果一样 等到下一个5s运行,当实际运行时间大于5s,当实际运行完会继续执行,而固定间隔的会再等5s才运行
  executorService.scheduleWithFixedDelay(() -> {
    Transaction transaction = Tracer.newTransaction("Apollo.ReleaseMessageScanner", "scanMessage");
    try {
      scanMissingMessages();
      scanMessages();
      transaction.setStatus(Transaction.SUCCESS);
    } catch (Throwable ex) {
      transaction.setStatus(ex);
      logger.error("Scan and send message failed", ex);
    } finally {
      transaction.complete();
    }
  }, databaseScanInterval, databaseScanInterval, TimeUnit.MILLISECONDS);
}

我们再看看它的线程工厂:

public class ApolloThreadFactory implements ThreadFactory {
  private static Logger log = LoggerFactory.getLogger(ApolloThreadFactory.class);
  // 计数器
  private final AtomicLong threadNumber = new AtomicLong(1);
  // 名称前缀
  private final String namePrefix;
  // 是否守护
  private final boolean daemon;
  // 线程组
  private static final ThreadGroup threadGroup = new ThreadGroup("Apollo");

  public static ThreadGroup getThreadGroup() {
    return threadGroup;
  }

  public static ThreadFactory create(String namePrefix, boolean daemon) {
    return new ApolloThreadFactory(namePrefix, daemon);
  }
  public Thread newThread(Runnable runnable) {
    Thread thread = new Thread(threadGroup, runnable,//
        threadGroup.getName() + "-" + namePrefix + "-" + threadNumber.getAndIncrement());
    thread.setDaemon(daemon);
    if (thread.getPriority() != Thread.NORM_PRIORITY) {
      thread.setPriority(Thread.NORM_PRIORITY);
    }
    return thread;
 }
}

可以看到跟 Tomcat 的线程工厂差不多,计数器、名称前缀,嘿嘿我们是不是能直接拿过来抄。

2.2.2  线程池的停止

咦,没看到有停止,服务停了就停了哈哈

3  小结

好啦,暂时看到这里哈,有理解不对的地方欢迎指正哈。

posted @ 2024-03-07 07:22  酷酷-  阅读(12)  评论(0编辑  收藏  举报