美文网首页
java线程池

java线程池

作者: 联想桥南 | 来源:发表于2017-12-16 22:50 被阅读0次
使用线程池的好处:
  • 降低资源消耗。重复创建创建和销毁造成浪费
  • 提高响应速度。相应的,利用已经创建好的线程,提高响应速度
  • 提高线程的可管理性。使用线程池方便统一的分配,调优和监控。

相应的想下,不用线程池,或者使用不当可能带来的问题

  • 如果你已经拥有足够多的线程使所有的CPU保持忙碌状态,那么创建更多的线程反而会降低性能
  • 操作系统的线程数的限制,JVM初始化参数,都是会对线程的创建起到约束的,无限的创建,很可能对导致OOM。
    比如:web服务,每个请求,使用单独的线程串行执行,效率不行,很多时间浪费在创建销毁线程上。
    改进下,如果每来一个请求,就使用创建一个单独的线程去处理的话,避免了上述情况。但是当用户很多或者恶意攻击的话。该负载就会导致问题。
    实际工程上一般使用线程池方案,管理线程,优化上述问题。
    比如常见的web容器tomcat,在处理请求的地方使用线程池:
    全局搜下ThreadPoolExecutor就可以看出来。
    在StandardThreadExecutor类里。
    protected void startInternal() throws LifecycleException {
        taskqueue = new TaskQueue(maxQueueSize);
        TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
        executor.setThreadRenewalDelay(threadRenewalDelay);
        if (prestartminSpareThreads) {
            executor.prestartAllCoreThreads();
        }
        taskqueue.setParent(executor);

        setState(LifecycleState.STARTING);
    }

可以看到使用的ThreadPoolExecutor类自己构造线程池,其中
队列是TaskQueue类(封装的LinkedBlockingQueue队列)。
ThreadFactory自己封装了下,添加了写个性化信息。
线程是大小corePoolSize25 maximumPoolSize200
maxIdleTime 1分钟

线程池的使用:

Executors线程池工具类,提供了以下四个工厂类方法:

  1. newFixedThreadPool
  2. newCachedThreadPool
  3. newSingleThreadExecutor
  4. newScheduledThreadPool

ExecutorService线程池抽象接口。
ThreadPoolExecutor实现类,继承ExecutorService接口,上述方法124返回结果的实现类。
ScheduledExecutorService继承ExecutorService接口,是3的实现类。

主要看下ThreadPoolExecutor的构造函数的参数,也就是涉及到线程池参数调优的部分。

ThreadPoolExecutor的参数信息:
corePoolSize 核心线程大小
maximumPoolSize 最大线程数大小
keepAliveTime 线程活动保持时间
unit keepAliveTime的单位
workQueue 任务队列
threadFactory 创建线程的工厂
handler 拒绝策略

处理流程:


线程池处理流程
  • 当任务到来的时候,线程池的corePoolSize未满,使用corePoolSize的线程处理。
  • 当corePoolSize满了后,任务队列未满的话,把任务添加到队列里。
  • 队列满的话,如果maximumPoolSize>corePoolSize的话,会创建新的线程,处理请求。
  • 如果maximumPoolSize满了,队列是有界队列的话,再添加任务,就会根据拒绝策略去决定怎么处理了。
  • 队列里的任务等着线程池里线程空闲,就会去处理。当队列里的任务处理完,线程就会空闲下来,判断空闲时间超过keepAliveTime,线程就会回收,知道线程数等于corePoolSize。
线程池参数调优及试用场景:

看任务是IO密集型还是计算密集型。

  • 如果是计算密集型,可以把线程数设置成N(cpu核数)+1。这样能够充分利用计算机cpu的处理能力。
  • 如果是IO密集型的,比如网络IO,磁盘的数据库IO,这种情况可以考虑把线程池的线程数调大些,比如2N(cpu核数)+1,目的也是充分利用了计算机的处理能力。
  • 如果程序里两种类型都有,可以考虑两个线程池。会比一个线程是 串行执行效果好。

上边的分析理想情况,都是基于程序单独部署的情况。如果是混部的话,实际效果不会那么好,因为不同的程序都会抢占cpu的时钟周期。

对于队
列的选择,如果执行的任务有优先级的选择,可是使用priorityQueue,一般会考虑使用FIFO队列LinkedBlockingQueue
如果任务有先后依赖关系考虑设置把coreSize=maxSize。

根据业考虑使用有界队列还是无界队列。
有界队列会丢数据(比如线程池任务数据库IO满查询),会使用拒绝策略抛出异常或者打印log。但是这不会影响程序其他的功能。
而无界队列,不会丢,有问题时队列长度会一直增加,如果没有处理的话。会导致OOM,整个JVM就挂了。
所以一般考虑使用有界队列。

ArrayBlockingQueue和LinkedBlockingQueue的区别
LinkedBlockingQueue:先进先出,默认是无界队列。有有界的构造方法。
阻塞队列,在put和take方法使用的是不同的锁。
ConcurrentLinkedQueue非阻塞队列,是用CAS操作保证线程安全。
ArrayBlockingQueue:先进先出,默认有界队列。阻塞队列,put和take使用的是一个锁。

ThreadFactory
守护线程与普通线程
JVM启动的时候,处理主线程,其他的线程都是守护线程。守护线程负责一些JVM辅助的工作,比如gc。主线程创建的线程都是普通线程。
当一个线程退出时,JVM会检查其他的所有线程,当都是守护线程的时候,JVM就正常退出。有任何一个普通线程还活着,JVM就不会退出。

拒绝策略的选择
ThreadPoolExecutor类里边提供了四种拒绝策略,不满足可以自己继承RejectedExecutionHandler接口自定义。

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

监控管理:
通过线程池提供的参数进行监控。ThreadPoolExecutor里的一些状态变量。

taskCount:线程池需要执行的任务数量。
completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不+ getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。

参考:
http://www.infoq.com/cn/articles/java-threadPool/
java并发编程实战
tomcat,jdk1.8

相关文章

网友评论

      本文标题:java线程池

      本文链接:https://www.haomeiwen.com/subject/saqjwxtx.html