JAVA线程池源码探究与简单小例子

学习线程池应该要先了解我们为什么需要它,假设我们需要建很多线程并发操作,如果每次start一个线程然后又销毁一个线程, 这样系统开销就很大,能不能减少它的开销,而又很好去管理他呢,线程池提供了这种可能性,就是它能使线程完成它的功能后不马上销毁,而去给另一线程使用。线程池功能大概像工厂调节工人数量分配任务这样子,线程池里的线程即相当于工厂里的工人,每个人手头有或没有任务,而新加入的线程都相当于任务分配给工人,线程池就能很好的调节工人数量,当所有工人都有任务了,那新分配任务就等待一下,老板就要看能不能再请多个工人,如果有工人没做事,或没事做,就要考虑炒了他,

从java 类包中看java线程池实现,平时主要使用几个线程池生成方式有

ThreadPoolExecutor executorService= (ThreadPoolExecutor) Executors.newCachedThreadPool();
ThreadPoolExecutor executorService1=(ThreadPoolExecutor) Executors.newScheduledThreadPool( corePoolSize);
ThreadPoolExecutor executorService2=(ThreadPoolExecutor) Executors.newFixedThreadPool( corePoolSize);
ExecutorService executorService3=  Executors.newSingleThreadScheduledExecutor();

注意返回的类型

ExecutorService 是接口而且继承了Executor接口

Executor只 有void execute(Runnable command);这个核心方法。

newCachedThreadPool里面:

 return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());

Executors里的几个静态方法(newCachedThreadPool、newScheduledThreadPool、newFixedThreadPool、newSingleThreadScheduledExecutor)这几个方法都实例化了ThreadPoolExecutor这个类,所以研究下它就能基本清楚线程池用法与原理,

public class ThreadPoolExecutor extends AbstractExecutorService,AbstractExecutorService implements ExecutorService,我们看下ThreadPoolExecutor 的四个构造方法:

 public ThreadPoolExecutor(int corePoolSize,//核心线程池大小,工厂里工人开始时的人数
                              int maximumPoolSize,//最大线程池大小,给工厂能限定一个工人容纳的数量。
                              long keepAliveTime,//线程没有任务执行时最多保持多久时间会终止,就是工人没任务干时还有多久解雇他。
                              TimeUnit unit,//时间单位
                              BlockingQueue<Runnable> workQueue) //阻塞队列,存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响。任务找不到工人干就暂时放这里。

{
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) //拒绝处理任务时的策略,有以下四种取值:

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

      AbortPolicy     、DiscardOldestPolicy四个都implements了 RejectedExecutionHandler且是ThreadPoolExecutor的静态内部类类

 { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler){..}

我们再看下newFixedThreadPool创建指定了工作线程数量,corePoolSize与maxPoolSize相等,发现keepAliveTime为0,即工人没事干马上就会被开除。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());//基于链表的阻塞队列,默认大小为Integer.MAX_Value
    }

newCachedThreadPool称为缓存池, 开始工作线程为0,需要再增加,这个每次任务进来都要新建线程,好像并没有发挥池的作用,所以要控制好任务数量不然系统开销很大,细心发现它的keepAliveTime为60秒即工人60秒没事干才会被开除,这时间段他能继续接受任务。就是60秒内存活的线程都可以让一个新线程无需再新建,至于你实际中任务量与工人数比例时间要根据实际情况可以改。调到最优。

最大就是Integer.MAX_VALUE 2^32-1

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());//一个不存储元素的阻塞队列,只有当一个元素被移除才能插入吞吐量比LinkedBlockingQueue大
    }

ScheduledExecutorService 计划的池,开始工作线程也为0,maximumPoolSize也跟缓存池一样,看到有个DelayedWorkQueue延迟到一定时间再取出元素

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue());
    }


下面我们看下一个小例子:

核心线程大小为5最大为10,数组阻塞队列大小为5,任务量为15.

public class DuiLie
{
public static void main(String[] args)
{ThreadPoolExecutor executer=new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5) );
for(int i=0;i<15;i++){

Runnable runnable =new TestRunnable();
executer.execute(runnable);
System.out.println("已完成任务量:"+executer.getCompletedTaskCount()+"\t缓存队列大小:"+executer.getQueue().size()
+"\t当前线程池线程数目:"+executer.getPoolSize());
}executer.shutdown();
}

}
class TestRunnable implements Runnable{


@Override
public void run()
{
System.out.println("执行了线程:"+Thread.currentThread().getName());
}


}

可以看到请求时新建了个线程pool-5-thread-1被重用了,任务一下请求过多的话,核心池大小就会new新线程,这里并不需要,没有看到第六个线程出现。

执行了线程:pool-5-thread-1
已完成任务量:0 缓存队列大小:0 当前线程池线程数目:1
已完成任务量:1 缓存队列大小:0 当前线程池线程数目:2
执行了线程:pool-5-thread-2
已完成任务量:2 缓存队列大小:0 当前线程池线程数目:3
执行了线程:pool-5-thread-3
已完成任务量:3 缓存队列大小:0 当前线程池线程数目:4
执行了线程:pool-5-thread-4
已完成任务量:4 缓存队列大小:0 当前线程池线程数目:5
已完成任务量:4 缓存队列大小:1 当前线程池线程数目:5
执行了线程:pool-5-thread-1
执行了线程:pool-5-thread-5
执行了线程:pool-5-thread-1
已完成任务量:4 缓存队列大小:1 当前线程池线程数目:5
已完成任务量:7 缓存队列大小:1 当前线程池线程数目:5
执行了线程:pool-5-thread-2
执行了线程:pool-5-thread-2
已完成任务量:7 缓存队列大小:1 当前线程池线程数目:5
已完成任务量:9 缓存队列大小:1 当前线程池线程数目:5
已完成任务量:9 缓存队列大小:2 当前线程池线程数目:5
执行了线程:pool-5-thread-4
已完成任务量:9 缓存队列大小:1 当前线程池线程数目:5
执行了线程:pool-5-thread-5
执行了线程:pool-5-thread-2
已完成任务量:10 缓存队列大小:1 当前线程池线程数目:5
执行了线程:pool-5-thread-1
执行了线程:pool-5-thread-4
已完成任务量:12 缓存队列大小:0 当前线程池线程数目:5
执行了线程:pool-5-thread-3
已完成任务量:14 缓存队列大小:0 当前线程池线程数目:5

而当任务量100时情况就不同了,抛出异常

已完成任务量:7 缓存队列大小:0 当前线程池线程数目:7
已完成任务量:8 缓存队列大小:1 当前线程池线程数目:7
执行了线程:pool-5-thread-4
执行了线程:pool-5-thread-4
已完成任务量:10 缓存队列大小:0 当前线程池线程数目:8
已完成任务量:10 缓存队列大小:1 当前线程池线程数目:8
已完成任务量:10 缓存队列大小:1 当前线程池线程数目:9
执行了线程:pool-5-thread-7
执行了线程:pool-5-thread-7
执行了线程:pool-5-thread-8
已完成任务量:13 缓存队列大小:0 当前线程池线程数目:10
已完成任务量:13 缓存队列大小:1 当前线程池线程数目:10
执行了线程:pool-5-thread-1
执行了线程:pool-5-thread-9
执行了线程:pool-5-thread-10
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task cn.service.TestRunnable@2f57816a rejected from java.util.concurrent.ThreadPoolExecutor@19f16e6e[Running, pool size = 10, active threads = 3, queued tasks = 0, completed tasks = 13]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
at cn.service.DuiLie.main(DuiLie.java:28)

看到Task cn.service.TestRunnable@39890510 rejected from java.util.concurrent.ThreadPoolExecutor@52ab7af2[Running, pool size = 10, active threads = 8, queued tasks = 1, completed tasks = 2]线程池达到极限10,而拒绝策略调用AbortPolicy拒绝任务执行,并抛出异常。

如果再将最大线程池数目改成20又能运行成功了,但缩小数组缓存队列到1又会出错。

再试了不同的队列SynchronousQueue<Runnable>(true) 公平竞争时同样抛异常       SynchronousQueue<Runnable>(false)默认情况可以成功,而LinkedBlockingQueue则线程池数量始终保持在核心池大小5,它的缓存队列也只达到4,如果把线程空闲存活时间缩短到到零缓存队列就比较大了,这种方法不会新建一个线程,只需把新请求放到缓存队列中去,因为LinkedBlockingQueue默认大小是Integer.MAX_Value.

其实通过这例子测试就发现了设置各种不同参数都对线程池执行有影响,那么怎么达到我们利用线程池更好管理线程和减少系统开销目标,就需要好好斟酌一下线程池大小

最大数,存活时间,缓存队列的关系了。一般有以下作参考:

  • 如果是CPU密集型应用,则线程池大小设置为N+1
  • 如果是IO密集型应用,则线程池大小设置为2N+1
  • 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

    比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:


    最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

    可以得出一个结论:

    线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。



致敬下科比,本人才疏学浅,大家有什么异议可以留言讨论下。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值