[Java并发系列]Java中的线程池

使用线程池可以对线程进行统一的分配、监控和调优,降低系统资源消耗,提升系统稳定性。

1. 使用线程池的好处

  1. 降低资源的消耗: 线程池通过重复利用线程中已存在的线程,从而降低了创建线程和销毁线程所造成的资源消耗。
  2. 提升响应速度: 当任务到达时,任务不需要等待创建线程,而直接使用线程池中已存在的线程就可以立即执行。
  3. 提高线程的可管理性: 使用线程池,可以对池中的线程进行统一的调度、监控,从而提升系统的稳定性。

2. 线程池工作原理

public void execute(Runnable command) {        
         if (command == null)            
             throw new NullPointerException();        
         /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();        
         //比较当前线程池中执行的线程数与核心线程池允许的最大线程数
        if (workerCountOf(c) < corePoolSize) {            
            if (addWorker(command, true))                
                return;
            c = ctl.get();
        }        
        if (isRunning(c) && workQueue.offer(command)) {            
            int recheck = ctl.get();            
            if (! isRunning(recheck) && remove(command))
                reject(command);            
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }        
       else if (!addWorker(command, false))            
            reject(command);
    }

线程池处理流程如下:

  1. 线程池判断核心线程池中的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务。如果核心线程里的线程都在执行任务,则进入下一个流程;
  2. 线程池判断工作队列是否已满,如果工作队列未满,则将任务添加到工作队列中,如果队列已满,则执行下一个流程;
  3. 线程池判断线程池是否已满,如果未满,则创建一个新的工作线程来执行任务,如果已满,则将任务交给饱和策略来处理任务;

3. 线程池饱和策略选择

在以上的线程池原理中提到了饱和策略,所谓的饱和策略就是当队列和线程池都满了,说明线程池处于饱和状态,那么就需要执行一种策略来处理提交的任务。以下是java线程池框架提供的4中饱和策略:

  • AbortPolicy(默认):直接抛出异常
  • CallerRunsPolicy:只用调用者所在线程来运行任务
  • DiscardOldestPolicy:丢弃对立中最近的一个任务,并执行当前任务
  • DiscardPolicy:不处理,直接丢弃任务

除了以上4中策略,还可以实现RejectedExecutionHandler接口,来自定义饱和策略,如记录日志或者持久化存储不能处理的任务。

  • kekeepAliveTime:线程活动保持时间:线程池中线程执行完毕任务空闲之后,允许存活的时间;
  • TimeUnit(线程活动保持时间的单位):可选的有天、小时、分钟、秒、毫秒、微妙、纳秒、千分之一毫秒、微秒。

4. 工作队列的选择

线程池工作队列就是用来存储等待执行任务的阻塞队列。可以选择一下的几种队列:

  • ArrayBlockingQueue:基于数组的有界阻塞队列,此队列按照FIFO的顺序对元素进行排序;
  • LinkedBlockingQueue:基于链表的有界阻塞队列,newSingleThreadExecutor线程池就使用了这种队列;
  • DelayedWorkQueue:使用优先级队列实现的无界阻塞队列,ScheduledThreadPoolExecutor线程池使用了这种阻塞队列;
  • SynchronousQueue: 不存储元素的阻塞队列,每个插入操作必须要等到另一个线程调用移除操作,否则插入操作会一直处于阻塞状态。newCachedThreadPool线程池使用了这种阻塞队列。

5. 线程池的使用

在使用线程池之前,首先需要了解创建一个线程池所必须要传入的几个参数:

public ThreadPoolExecutor(int corePoolSize,                              
                              int maximumPoolSize,                              
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {        
           if (corePoolSize < 0 || maximumPoolSize <= 0 ||
                    maximumPoolSize < corePoolSize ||keepAliveTime < 0)            
                throw new IllegalArgumentException();       
           if (workQueue == null || threadFactory == null || 
                    handler == null)            
               throw new NullPointerException();        
           this.corePoolSize = corePoolSize;        
           this.maximumPoolSize = maximumPoolSize;        
           this.workQueue = workQueue;        
           this.keepAliveTime = unit.toNanos(keepAliveTime);        
           this.threadFactory = threadFactory;        
           this.handler = handler;
    }
  • corePoolSize(线程池基本的大小):当向线程池中提交一个任务时,会创建一个线程来执行任务,即使其他空闲的线程也能执行任务,只有当需要执行的任务数大于线程池基本大小时,才不能创建新的线程;
  • maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且创建的线程数小于最大线程数量,那么就会创建新的线程来执行任务;
  • workQueue(阻塞队列):如第三节;
  • threadFactory:用于设置创建线程的工厂,可以通过线程工厂,给每个创建出来的线程设置更有意义的名字。

6. 向线程池中提交任务

向线程池中提交任务,有两种方式:execute()和submit()

  • execute(): 提交任务但不需要返回值,无法判断任务是否被执行成功;
  • submit(): 用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象,可以判断任务是否执行成功,并且可以通过future的get()方法,来获取返回值。

7. 线程池的监控

线程池主要是对线程进行统一的资源调控、分配和监控,当线程池中线程出现问题时,可以根据线程池中提供的一些方法参数进行迅速的定位,以下API是常用的用于监控线程池的方法和属性:

  • public int getPoolSize():返回线程池中线程数量;
  • public int getLargestPoolSize():返回线程池中曾经创建过的最大线程数量。通过这个数据而已知道线程池是否曾经满过。如该数值等于线程池的最大大小,表示线程池曾经满过;
  • public long getTaskCount():线程池需要执行的任务数量
  • public long getCompletedTaskCount():获取已完成的任务数量

除了以上这些方法,还可以通过继承线程池来自定义线程池,重写线程池中的一些方法,如terminated()、afterExecute()、beforeExecute(),通过重写这几个方法,就可以实现在线程池关闭、任务执行后、任务执行前对线程进行监控。

原文发布于微信公众号 - 瞎说开发那些事(jsj201501)

原文发表时间:2017-10-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2717
来自专栏杨龙飞前端

scrollto 到指定位置

2534
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

2665
来自专栏Golang语言社区

【Golang语言社区】GO1.9 map并发安全测试

var m sync.Map //全局 func maintest() { // 第一个 YongHuomap := make(map[st...

4778
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3195
来自专栏闻道于事

js登录滑动验证,不滑动无法登陆

js的判断这里是根据滑块的位置进行判断,应该是用一个flag判断 <%@ page language="java" contentType="text/html...

6948
来自专栏大内老A

The .NET of Tomorrow

Ed Charbeneau(http://developer.telerik.com/featured/the-net-of-tomorrow/) Exciti...

32010
来自专栏落花落雨不落叶

canvas画简单电路图

63311
来自专栏pangguoming

Spring Boot集成JasperReports生成PDF文档

由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲...

1.2K7
来自专栏转载gongluck的CSDN博客

cocos2dx 打灰机

#include "GamePlane.h" #include "PlaneSprite.h" #include "BulletNode.h" #include...

5566

扫码关注云+社区