线程池理解

生命周期

execute--------shutdown()/shutdownNow()---------tiding---------terminate()---------terminated

  • shutdown():线程池不再接受任务。已有的任务(包括队列的)执行完后线程池关闭。
  • shutdownNow():线程池不再接受任务。立即关闭线程池。正在执行的任务也会直接中断。
  • terminate():调用shutdown或shutdownNow后。线程池进入无任务的tiding(疲劳)状态。然后马上执行一个钩子函数terminate()被terminate(结束)。

构造参数

总览

new ThreadPoolExecutor(
int corePoolSize,   //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //线程活跃时间(线程回收相关)
TimeUnit unit, //线程活跃时间单位
BlockingQueue<Runnable> workQueue, //任务队列
ThreadFactory threadFactory,  //线程工厂函数
RejectedExecutionHandler handler //任务拒绝异常处理
)

详解

corePoolSize:

核心线程数

给线程池添加任务时,线程池会分配线程去异步执行任务

如果任务的数量超过核心线程数,会把任务放到任务队列中

maximumPoolSize:

最大线程数

前面说了,如果任务的数量超过核心线程数,会把任务放到任务队列中

那么如果任务队列也满了会怎么样?

这时如果再添加任务,那么线程池就会根据最大线程数开新的线程

前提是总的线程数不能超过最大线程数

keepAliveTime:

线程活跃时间

有任务时,线程池会分配线程去执行任务

当一条线程把这个任务执行完毕时,这个线程就会进入空闲状态,这时这个线程或许应当被回收

这个参数就是表示线程进入空闲状态后达到多长时间就会被回收

TIPS:如果这个参数为0,则表示线程不会被回收

unit:

线程活跃时间单位

也就是上个参数线程活跃时间的单位

是枚举类

有毫秒、秒、分钟、小时、日、等等

workQueue:

任务队列

如果任务的数量超过核心线程数,会把任务放到任务队列中

队列的构造如果不设置队列长度参数

那么队列默认会是会动态扩充的

最大长度是int的最大值2147483647

threadFactory:

线程工厂函数

线程池的线程是通过这个函数创建的

线程池有个默认的实现

也可以自己实现这个函数

handler:

任务拒绝异常处理类

如果任务的数量超过核心线程数,会把任务放到任务队列中

如果任务队列也满了,再添加任务就会根据最大线程数去开新的线程

如果线程数也达到了最大线程数,爆满了

这时再添加任务就会进入任务拒绝异常处理

这个异常处理类实现接口类RejectedExecutionHandler

ThreadPoolExecutor有4种已经实现的任务拒绝异常处理类(并且是ThreadPoolExecutor的内部类)

1.AbortPolicy:中止策略

仅仅打印出异常的相关任务和线程池

源码:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}

2.DiscardPolicy:抛弃政策

默默丢弃这个任务,什么也不做

源码:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}

3.DiscardOldestPolicy:抛弃最老政策

丢弃队列最前面的任务

源码:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        e.getQueue().poll();
        e.execute(r);
    }
}

TIPS:队列的poll方法:移除队列的第一个元素并返回这个对象,如果队列为空,则会返回null。

4.CallerRunsPolicy:调用者政策

由线程池的调用者来执行这个任务

源码:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
        r.run();
    }
}

TIPS:源码中判断一下线程池是否已关闭,才选择是否执行这个任务,这只是一种逻辑上的关系,因为任务已经是由线程池之外的线程(线程池的调用者)执行了,这两者实际上并没有直接的关系

常见的几种线程池

CachedThreadPool

这种线程池核心线程数为0,最大线程数为2147483647(int最大值),线程回收等待时间为60秒,任务队列为SynchronousQueue(一种长度容量为0的队列)。

源码:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(
			0, 
			Integer.MAX_VALUE,
			60L,
			TimeUnit.SECONDS,
			new SynchronousQueue<Runnable>());

    }

也就是说这种线程池每次添加任务都会直接使核心线程数和任务队列爆满,从而每次添加任务都会创建新线程,可以说是弹性非常大。

总结它的特点:

  • 线程的创建数量几乎没有限制(其实也有限制的,数目为2147483647(int最大值)), 这样可灵活的往线程池中添加线程。
  • 如果长时间没有往线程池中提交任务,即如果工作线程空闲了60秒,则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池新创建一个线程。
  • 在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
  • 这种线程池在执行 大量短生命周期的异步任务时(many short-lived asynchronous task),可以显著提高程序性能。

FixedThreadPool

填满的线程池

最大线程数等于核心线程数的线程池

如果当所有线程都是活动时,有多的任务被提交过来,那么它会一致在队列中等待直到有线程可用。

源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(
			nThreads, 
			nThreads,
			0L, 
			TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<Runnable>());
    }

TIPS:注意队列实例是:new LinkedBlockingQueue,是一种共享的无边界队列

SingleThreadPool

只有一个线程的线程池

源码:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(
        	new ThreadPoolExecutor( 
			1, 
			1, 
			0L, 
			TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<Runnable>()));
    }

ScheduledThreadPoolExcutor

定时线程池

有以下4个调度器的方法:

public ScheduledFuture<?> schedule(
			Runnable command,
			long delay, 
			TimeUnit unit
);

public <V> ScheduledFuture<V> schedule(
			Callable<V> callable,
			long delay, 
			TimeUnit unit
);

public ScheduledFuture<?> scheduleAtFixedRate(
			Runnable command,
			long initialDelay,
			long period,
			TimeUnit unit
);

public ScheduledFuture<?> scheduleWithFixedDelay(
			Runnable command,
			long initialDelay,
			long delay,
			TimeUnit unit
 );

方法1: 进行一次延迟调度:延迟delay的时间,单位为:Time Unit传入的的一个基本单位,例如:TimeUnit.SECONDS属于提供好的枚举信息:。

方法2: 多次调度,每次依照上一次预计调度时间进行调度,例如:延迟2s开始,5s一次,那么就是2、7、12、17,如果中间由于某种原因导致线程不够用,没有得到调度机会,那么接下来计算的时间会优先计算进去,因为他的排序会被排在前面,也就是先排好再按理论上的时间执行。

方法3: 多次调度,每次按照上一次实际执行的时间进行计算下一次时间,同上,如果在第7秒没有被得到调度,而是第9s才得到调度,那么计算下一次调度时间就不是12秒,而是9+5=14s,如果再次延迟,就会延迟一个周期以上,也就会出现少调用的情况,也就是按实际的完成时间来计算下次执行时间。

方法4: 最后补充execute方法是一次调度,期望被立即调度,时间为空。

TIPS:Callable和Runnable基本相同,唯一的区别是Callable有返回值而Runnable没有。

posted @ 2018-08-21 15:47  CodeOfC  阅读(89)  评论(0)    收藏  举报