java线程池入门

前言

使用线程池有以下好处:

  • 创建销毁线程消耗系统资源,线程池可以复用已创建的线程。
  • 控制并发的数量,并发数量过多,可能导致资源不足,服务器崩溃。
  • 对线程进行统一管理。

简单使用

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;
import java.util.concurrent.TimeUnit;

public class Client {

  public static void main(String[] args) {
    ExecutorService executorService = new ThreadPoolExecutor(
        5,
        10,
        60,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(),
        Executors.defaultThreadFactory(),
        new AbortPolicy()
    );
    executorService
        .execute(() -> System.out.println(Thread.currentThread().getName() + " is running"));
    executorService.shutdown();
  }

}

参数分析

线程池的核心实现为ThreadPoolExecutor类,

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
}

一共有7大重要参数

  • corePoolSize
表示核心线程数量,线程池中有两种线程,核心线程和非核心线程(临时工)
  • maximumPoolSize
表示线程总数量,核心数+非核心数
  • keepAliveTime
表示非核心线程闲置超时时长,超过了这个时间,线程就会被回收,如果设置了allowCoreThreadTimeOut(true),也会作用于核心线程
  • unit
时间单位,主要有时(HOURS),分(MINUTES),秒(SECONDS),毫秒(MILLISECONDS),微妙(MICROSECONDS),纳秒(NANOSECONDS)
  • workQueue
任务队列,当任务数量超过核心线程数时,就会将任务添加到任务队列中。
常用的队列类型有
1.LinkedBlockingQueue,阻塞队列,底层实现为链表,默认大小为Integer.MAX_VALUE,可以设置大小
2.ArrayBlockingQueue,阻塞队列,底层实现为数组,必须指定大小
3.SynchronousQueue,同步队列,容量为0,添加操作必须等待消费操作,反之亦然
4.DelayQueue,延时队列,只有到达了延时时间,才能从队列中获取到
  • threadFactory
    表示创建线程的工厂,默认实现为
/**
     * The default thread factory.
     */
    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
  • handler
    表示拒绝策略,当任务数量大于线程最大容量时,就会执行拒绝策略,有4种实现
1.AbortPolicy,直接抛异常
2.DiscardPolicy,直接丢弃,不抛异常
3.DiscardOldestPolicy,丢弃任务队列头部元素,重新尝试执行
4.CallerRunsPolicy,由调用线程来执行

上述7个参数,前5个为必须,后2个非必须。

线程池的处理流程

  1. 线程总数量 < corePoolSize,⽆论线程是否空闲,都会新建⼀个核心线程执⾏任务。
  2. 线程总数量 >= corePoolSize时,新来的线程任务会进⼊任务队列中等待,然后空闲的核心线程会依次去缓存队列中取任务来执行(体现了线程复用)。
  3. 当缓存队列满了,说明这个时候任务已经多到爆棚,需要⼀些“临时⼯”来执⾏ 这些任务了。于是会创建⾮核心线程去执行这个任务。
  4. 缓存队列满了,且总线程数达到了maximumPoolSize,则会执行拒绝策略。

线程池状态

线程池一共有5种状态,

  1. RUNNING,线程池创建后为RUNNING状态
  2. SHUTDOWN,调用shutdown()方法后处于SHUTDOWN状态,此时不能接收新任务,中断所有未执行的线程,等待任务队列中任务完成。
  3. STOP,调用shutdownNow()方法后处于STOP状态,此时不能接收新任务,中断所有正在执行的线程,任务队列中未执行任务全部丢弃。
  4. TIDYING,当所有任务已终止,线程池中任务数量为0,此时状态为TIDYING
  5. TERMINATED,执行完terminated()方法,状态由TIDYING变成TERMINATED

4种常用的线程池

jdk提供了一个工具类Executors,简化我们创建线程池的过程

  • newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

不会创建核心线程,全部为非核心线程(临时工),超过60S回收。适合执行有很多短时间的任务。

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

只创建核心线程,不创建非核心线程,超过数量全部进任务队列。

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

只有一个核心线程,和 newFixedThreadPool(1) 的区别为,newFixedThreadPool(1)创建的线程池是可以重新配置的,如重新设置核心线程数量,而newSingleThreadExecutor()创建的线程池不能重新配置。

  • newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
}

ScheduledThreadPoolExecutor继承于ThreadPoolExecutor,支持定时任务及周期性任务。

参考

线程池,这一篇或许就够了
Java线程池实现原理与源码解析(jdk1.8)

posted @ 2021-06-13 00:45  strongmore  阅读(110)  评论(0编辑  收藏  举报