深入学习Java中的线程池

目录

  • 什么是线程池?
  • 线程池有什么优势?
  • 线程池的使用
    • Executors创建指定类型的线程池
      • newSingleThreadExecutor
      • newFixedThreadPool
      • newCachedThreadPool
      • newScheduledThreadPool
      • newSingleThreadScheduledExecutor
    • ThreadPoolExecutor创建自定义线程池
  • 线程池的工作原理
  • 功能线程池
    • 定长线程池(FixedThreadPool)
    • 定时线程池(ScheduledThreadPool )
    • 可缓存线程池(CachedThreadPool)
    • 单线程化线程池(SingleThreadExecutor)

什么是线程池?

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。

线程池有什么优势?

有以下优势:

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立刻执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果是无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进统一的分配,调优和监控。

深入学习Java中的线程池_第1张图片

线程池的使用

Executors创建指定类型的线程池

内部实现基于ThreadPoolExecutor,固定参数。

newSingleThreadExecutor

创建只有一个工作线程数量的线程池,它只会用一个工作线程来执行任务,保证所有任务按照指定顺序(FIFO、LIFO、 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

ExecutorService es1 = Executors.newSingleThreadExecutor();

newFixedThreadPool

创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中(阻塞队列,用来存储等待执行的任务)。

FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

ExecutorService es2 = Executors.newFixedThreadPool(3);

newCachedThreadPool

创建一个可缓存的(工作线程数量可变)线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:

工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。

如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的(keepAliveTime)时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。

在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统OOM。

ExecutorService es3 = Executors.newCachedThreadPool();

newScheduledThreadPool

创建指定工作线程数并定时周期性执行任务的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

ScheduledExecutorService es4 = Executors.newScheduledThreadPool(2);

newSingleThreadScheduledExecutor

创建单个工作线程数并定时周期性执行任务的线程池,它可安排在给定延迟后运行命令或者定期地执行。线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行并且可定时或者延迟执行线程活动。

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

ThreadPoolExecutor创建自定义线程池

创建自定义线程池,参数可自定义配置。

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                   long keepAliveTime,
                   TimeUnit unit,
                   BlockingQueue<Runnable> workQueue);

可以看到,其需要如下几个参数:

corePoolSize:线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法预创建线程,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列(workQueue)当中;

maximumPoolSize:线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。这里值得一提的是largestPoolSize,该变量记录了线程池在整个生命周期中曾经出现的最大线程个数。为什么说是曾经呢?因为线程池创建之后,可以调用setMaximumPoolSize()改变最大线程数。

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。当线程池中的线程数大于corePoolSize且一个线程空闲的时间达keepAliveTime,则该线程会被销毁。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

unit:参数keepAliveTime的时间单位,有7种取值,在java.util.concurrent.TimeUnit类中:

  1. imeUnit.DAYS             天
  2. TimeUnit.HOURS          小时
  3. TimeUnit.MINUTES         分钟
  4. TimeUnit.SECONDS        秒
  5. TimeUnit.MILLISECONDS   毫秒
  6. TimeUnit.MICROSECONDS  微妙
  7. TimeUnit.NANOSECONDS   纳秒

任务队列-workQueue:一个阻塞队列 BlockingQueue,用来存储等待执行的任务(Runnable),这个参数的选择也很重要,会对线程池的运行过程产生重大影响,阻塞队列有以下几种选择,都是BlockingQueue的实现:

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
  3. SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
  4. PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
  5. DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
  6. LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
  7. LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

ArrayBlockingQueuePriorityBlockingQueue使用较少,一般使用LinkedBlockingQueueSynchronousQueue。线程池的排队策略与BlockingQueue有关。

注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

线程工厂-threadFactory:用于设置创建线程的工厂 ThreadFactory,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如设置daemon和优先级等等
Executors 框架已经为我们实现了一个默认的线程工厂:

/**
 * 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:表示当拒绝处理任务时的策略 RejectedExecutionHandler,有以下四种取值:默认策略AbortPolicy

RejectedExecutionHandler defaultHandler = new AbortPolicy();
  • AbortPolicy:直接抛出异常(默认)。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
  • 自定义策略:实现RejectedExecutionHandler接口。
  • workers:线程池中的工作线程都放在此集合 HashSet中,Worker是线程池中的线程,而Task虽然是runnable,但是并没有真正执行,只是被Worker调用了run方法。

    Worker类本身既实现了Runnable,又继承了AbstractQueuedSynchronizer(以下简称AQS),所以其既是一个可执行的任务,又可以达到锁的效果,Worker实现的AQS为不可重入锁,为了在获得worker锁的情况下再进入其它一些需要加锁的方法。

    HashSet<Worker> workers = new HashSet<Worker>();
    

    mainLock:线程执行任务时候使用的加锁。

    ReentrantLock mainLock = new ReentrantLock();
    

    termination:加锁使用的条件

    Condition termination = mainLock.newCondition();
    

    线程池的工作原理

    下面来描述一下线程池工作的原理,同时对上面的参数有一个更深的了解。其工作原理流程图如下:
    深入学习Java中的线程池_第2张图片

    功能线程池

    上面使用线程池的方法太麻烦?其实Executors已经为我们封装好了 4 种常见的功能线程池,如下:

    定长线程池(FixedThreadPool)
    定时线程池(ScheduledThreadPool )
    可缓存线程池(CachedThreadPool)
    单线程化线程池(SingleThreadExecutor)
    

    定长线程池(FixedThreadPool)

    创建方法的源码:

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

    特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
    应用场景:控制线程最大并发数。

    示例

    		// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		fixedThreadPool.execute(task);
    

    定时线程池(ScheduledThreadPool )

    创建方法的源码:

    		private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
    		 
    		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());
    		}
    		 
    		public static ScheduledExecutorService newScheduledThreadPool(
    		        int corePoolSize, ThreadFactory threadFactory) {
    		    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    		}
    		public ScheduledThreadPoolExecutor(int corePoolSize,
    		                                   ThreadFactory threadFactory) {
    		    super(corePoolSize, Integer.MAX_VALUE,
    		          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    		          new DelayedWorkQueue(), threadFactory);
    		}
    

    特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
    应用场景:执行定时或周期性的任务。

    示例:

    		// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    		scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
    

    可缓存线程池(CachedThreadPool)

    创建方法的源码:

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

    特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
    应用场景:执行大量、耗时少的任务。

    示例

    		// 1. 创建可缓存线程池对象
    		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		cachedThreadPool.execute(task);
    

    单线程化线程池(SingleThreadExecutor)

    创建方法的源码:

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

    特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
    应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

    示例

    		// 1. 创建单线程化线程池
    		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    		// 2. 创建好Runnable类线程对象 & 需执行的任务
    		Runnable task =new Runnable(){
    		  public void run() {
    		     System.out.println("执行任务啦");
    		  }
    		};
    		// 3. 向线程池提交任务
    		singleThreadExecutor.execute(task);
    

    深入学习Java中的线程池_第3张图片
    参考:Android多线程:线程池ThreadPool全面解析
    Java 多线程:彻底搞懂线程池
    java中的线程池

你可能感兴趣的:(JavaEE,java,学习,jvm)