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

相关文章

来自专栏技术碎碎念

Java线程的几种状态

java.lang.Thread.State中定义的集中Java线程的状态: 1 /** 2 * A thread state. A thread c...

3466
来自专栏Java技术栈

java高级应用:线程池全面解析

什么是线程池? 很简单,简单看名字就知道是装有线程的池子,我们可以把要执行的多线程交给线程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的...

3368
来自专栏博岩Java大讲堂

Java集合--阻塞队列(引言)

32912
来自专栏奔跑的蛙牛技术博客

Java并发知识点(2)

用new操作符创建一个新线程时如new Thread(r)  该线程还没有被运行,这意味着这个线程的状态是new。当一个线程处于新创建状态,程序还没有开始运行线...

642
来自专栏小鄧子的技术博客专栏

简单线程池的实现

要使用上述线程池,还需要一个永不退出的工作现场与之配合。是一个While循环,手动关闭之前,永不结束,一直等待新的任务进来。

881
来自专栏Linyb极客之路

并发编程之线程池

一、关于ThreadPoolExecutor 为了更好地控制多线程,JDK提供了一套Executor框架,帮助开发人员有效的进行线程控制,其本质就是一个线程池。...

4188
来自专栏你不就像风一样

Java线程池实现原理与技术(ThreadPoolExecutor,Executors)

多线程的软件设计方法确实可以最大限度地发挥多核处理器的计算能力,提高生产系统的吞吐量和性能。但是,若不加控制和管理的随意使用线程,对系统的性能反而会产生不利的影...

995
来自专栏Android开发指南

4.线程池

34512
来自专栏xingoo, 一个梦想做发明家的程序员

Java计数器之CountDownLatch、CyclicBarrier、Semaphore

在Java里面有几种可以用于控制线程状态的方法,如CountDownLatch计数器、CyclicBarrier循环栅栏、Sempahore信号量。下面就分别...

3357
来自专栏博客园迁移

CountDownLatch和CyclicBarrier模拟同时并发请求

  有时候要测试一下某个功能的并发能力,又不要想借助于其他测试工具,索性就自己写简单的demo模拟一个并发请求就最方便了。如果熟悉jemter的测试某接口的并发...

701

扫码关注云+社区