[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 条评论
登录 后参与评论

相关文章

来自专栏鬼谷君

saltstack 初始化LINUX系统

前面我们已经了解了saltstack的基础功能,现在就可以使用saltstack为初始化新安装的linux系统。

1696
来自专栏用户2442861的专栏

Tomcat源码 Connector(2)

 Connector是Tomcat最核心的组件之一,负责处理一个WebServer最核心的连接管理、Net IO、线程(可选)、协议解析和处理的工作。 一、...

921
来自专栏智能大石头

NewLife.Net——开始网络编程

网络编程的重要性就不说了,先上源码:https://github.com/nnhy/NewLife.Net.Tests

880
来自专栏Python

解决Python自带的json不能序列化data,datetime类型数据问题

官方文档中的一个Demo: >>> import json >>> class ComplexEncoder(json.JSONEncoder): ... ...

3563
来自专栏Felix的技术分享

《一个操作系统的实现》笔记(2)--保护模式

3008
来自专栏Golang语言社区

Golang语言社区--【基础知识】go安装步骤

解压go语言安装包 tar -zxvf go1.7beta1.linux-amd64.tar.gz 环境变量配置 vim .bash_profile expor...

3388
来自专栏技术博客

Asp.Net MVC 3.0 使用Gzip压缩

Gzip最早由Jean-loup Gailly和Mark Adler创建,用于Unix系统的文件压缩。我们在Linux中经常会用到后缀为.gz的文件,它们就是G...

1052
来自专栏分布式系统进阶

Influxdb 编译

1261
来自专栏IT笔记

Linux下安装memcached之Tomcat7集群

这两天,在捣鼓负载均衡的问题,使用的是memcached做session存储。但是你造吗?居然没有成功,都硕失败是成功之母,我想我快成功了。 ? 安装环境 ce...

3669
来自专栏程序猿

Burp Suite第十五节:BurpSuite全局参数设置和使用

在Burp Suite中,存在一些粗粒度的设置,这些设置选项,一旦设置了将会对Burp Suite的整体产生效果,这就是Burp Suite中Op...

5408

扫码关注云+社区