前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Java并发系列]Java中的线程池

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

作者头像
用户2017109
发布2018-06-19 11:19:11
6210
发布2018-06-19 11:19:11
举报

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

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(),通过重写这几个方法,就可以实现在线程池关闭、任务执行后、任务执行前对线程进行监控。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-10-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 瞎说开发那些事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 使用线程池的好处
  • 2. 线程池工作原理
  • 3. 线程池饱和策略选择
  • 4. 工作队列的选择
  • 5. 线程池的使用
  • 6. 向线程池中提交任务
  • 7. 线程池的监控
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档