Java自带线程池的解析

线程池是什么?

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。(来自百度百科)

说白了就是 我是一个包工头,我手下有一堆工人。当没有工作的时候,他们就处在闲下来的状态。当工作来了。他们就要出去工作。当工作忙不过来的时候。 作为包工头的我自然会再去招聘一些工人。然后等到工作忙完了。空下来的一段时间后,发现用不了这么多人了。我就需要辞退掉一些人。

那么Java中给我们默认提供哪几种线程池呢?

Java中自带的线程池都是由 java.util.concurrent.Executors; 这个类创建的。 默认为我们提供了

ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newWorkStealingPool = Executors.newWorkStealingPool();
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);

这五种实现。

newFixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

这个就是newFixedThreadPool 的具体实现。 他创建了一个ThreadPoolExecutor 这个类。 这个类的主要参数说明如下

/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters and default thread factory.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 * @param unit the time unit for the {@code keepAliveTime} argument
 * @param workQueue the queue to use for holding tasks before they are
 *        executed.  This queue will hold only the {@code Runnable}
 *        tasks submitted by the {@code execute} method.
 * @param threadFactory the factory to use when the executor
 *        creates a new thread
 * @param handler the handler to use when execution is blocked
 *        because the thread bounds and queue capacities are reached
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code handler} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

corePoolSize: 保留线程池中的核心线程数,即使他们空闲也不会被销毁。除非设置了allowCoreThreadTimeOut 参数。 允许 核心线程超时
maximumPoolSize: 线程池中的最大线程数。
keepAliveTime: 当线程大于核心数的时候,多余出来的空闲线程的存活时间
TimeUnit : 时间单位
workQueue : 用于保存任务的队列。此队列只用于存储 Runable
handler : 当工作队列满的时候。用来处理 队列满了的拒绝添加任务的策略。
threadFactory: 线程工厂。用于生产线程的工厂

线程池中当我们队列中的任务没有满的时候。就会默认使用核心线程数来工作。但是当我们的工作队列已经满了。这个时候,线程池会使用 threadFactory 创建出最大线程数的线程数量。此时的总线程数量为 maxmunPoolSize。 也就是说。 线程池中能够存放 maximunPoolSize + workQueue.length 的数量任务。当数量超出的时候,就会使用 handler 中的拒绝策略。来放弃其他任务还是使用主线程去处理。

看了以上的解释后,我们再看回头看默认提供的线程池吧。 newFixedThreadPool 核心现成和最大线程是相同的。
因为核心线程和最大线程数是相同。所以 keepAliveTime 的时间为 0。 这里使用了一个LinkedBlockingQueue。 这里使用的是一个 链表类型的阻塞队列。 这个队列。可以说是无界的可以理解为无限大。

public ThreadPoolExecutor(int corePoolSize,
                    int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue) {
   this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
        Executors.defaultThreadFactory(), defaultHandler);
}

这里是newFixedThreadPool 调用的 构造方法。 可以看出来。这里的threadFactory. 是默认的线程工厂。和一个默认的拒绝策略。我们自己实现线程池的时候。也可以选择使用默认的线程工厂。但是拒绝策略最好还是自己动手写一下。

newFixedThreadPool 可以看出来是一个固定线程数量的线程池。并且他的队列是无边界的链表队列。

newCachedThreadPool

/**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

可以看出来, 这个线程池的核心线程数为 0 个, 但是他的最大线程数量是 int 类型的最大值。 并且他的工作队列是一个 SynchronousQueue 队列。这个队列是一个只能存储一个元素的队列。当我存在一个元素的时候,你就只能等着另一个线程取走这一个元素你才可以插入这个元素。 所以可以看出来,这个线程池的特点是。 来一个任务我就直接创建一个新的线程将它取走并完成它。如果任务量巨大的话。 最大线程数量是 int 类型的最大值。 很可能就OOM了

扫描二维码关注公众号,回复: 10643084 查看本文章
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
	at java.lang.Thread.start0(Native Method)
	at java.lang.Thread.start(Thread.java:717)
	at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:957)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1378)
	at cn.fllday.thread.poiol.CustomThreadPool.main(CustomThreadPool.java:21)

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
  return new FinalizableDelegatedExecutorService
      (new ThreadPoolExecutor(1, 1,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>()));
}

这个线程池,我们从名字就可以看出来了。 Single 单个线程的线程池。 最大线程数和核心线程数都是 1 。 并且使用的是 链表阻塞队列。 也就是无界的。

newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
  return new ScheduledThreadPoolExecutor(corePoolSize);
}

来看看这个类的构造函数吧

/**
* Creates a new {@code ScheduledThreadPoolExecutor} with the
 * given core pool size.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

这个类继承自 ThreadPoolExecutor 懂了吧。 其实也就是调用了 ThreadPoolExecutor
那么这个线程池的核心线程数就是 我们传入的 corePoolSize。 最大线程数是 int 类型的最大值。 工作队列为 DelayedWorkQueue。有意思的是这个线程池可以延迟执行方法和定时执行方法。

Runnable commnd = new Runnable() {
   @Override
   public void run() {
       System.out.println("延迟打印" + System.currentTimeMillis());
   }
};
/** 延迟一秒后打印一次 只执行一次 **/
//        newScheduledThreadPool.schedule(commnd,1,TimeUnit.SECONDS);
/** 延迟一秒后。打印,然后每两秒打印一次**/
newScheduledThreadPool.scheduleWithFixedDelay(commnd,1,2,TimeUnit.SECONDS);
/** 延迟一秒后。打印,然后每一秒打印一次**/
newScheduledThreadPool.scheduleAtFixedRate(commnd, 1,1, TimeUnit.SECONDS);

newWorkStealingPool

这个我就不说了。因为我也没弄懂。 弄懂了再补上吧。我只知道这是个精灵线程。 在后台运行,之前的线程 main 会等着 线程池的任务完成后才会结束。这个不会。这个main 方法结束。 精灵线程也就没了。也就是说。有可能虚拟机推出了。你这个精灵线程还没有执行完。
代码示例

System.out.println("我是主线程。即将启动精灵线程");
newWorkStealingPool.execute(()->{
    try {
        System.out.println("我是精灵线程开始沉睡 2000");
        Thread.sleep(2000);
        System.out.println("我是精灵线程");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
Thread.sleep(1000);
System.out.println("我是主线程。我已经执行完毕");

打印结果

我是主线程。即将启动精灵线程
我是精灵线程开始沉睡 2000
我是主线程。我已经执行完毕
发布了167 篇原创文章 · 获赞 28 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_40990836/article/details/105422492