线程池原理

1. 使用线程池的原因:

1.创建/销毁线程需要消耗系统资源,线程池可以复用已经创建的线程。

2.控制并发的数量。并发数量过多,可能导致资源消耗过多,从而千万服务器崩溃。(主要原因)

3.可以对线程做统一管理。

2线程池的原理

java中的线程池顶层接口是 Executor接口,ThreadPoolExecutor 是这个接口的实现类。

2.1 ThreadPoolExecutor 提供的构造方法

构造方法:

ThreadPoolExecutor(int corePoolSize, 	//线程池中核心线程数最大值
    				int maximumPoolSize, 	//线程池中线程总数最大值
        			long keepAliveTime, 	//非核心线程闲置超时时长
           		TimeUnit unit, 			//keepAliveTime的单位枚举类型,属性包括:微毫秒到天
             		BlockingQueue<Runnable> workQueue) //阻塞队列,维护着等待执行的Runnable任务对象
          用给定的初始参数和默认的线程工厂及被拒绝的执行处理程序创建新的 ThreadPoolExecutor。 
ThreadPoolExecutor(int corePoolSize, 
                    int maximumPoolSize, 
                    long keepAliveTime, 
                    TimeUnit unit, 
                    BlockingQueue<Runnable> workQueue, 
                    RejectedExecutionHandler handler) 
          用给定的初始参数和默认的线程工厂创建新的 ThreadPoolExecutor。 
ThreadPoolExecutor(int corePoolSize, 
                    int maximumPoolSize, 
                    long keepAliveTime, 
                    TimeUnit unit, 
                    BlockingQueue<Runnable> workQueue, 
                    ThreadFactory threadFactory) 
          用给定的初始参数和默认被拒绝的执行处理程序创建新的 ThreadPoolExecutor。 
ThreadPoolExecutor(int corePoolSize, 
                    int maximumPoolSize, 
                    long keepAliveTime, 
                    TimeUnit unit, 
                    BlockingQueue<Runnable> workQueue, 
                    ThreadFactory threadFactory, 	
                    //创建线程的工厂,用于指创建线程,统一在创建线程时设置一些参数,如是否守护线程、线程的优先级等,如果不指定,会新建一个默认的线程工厂
                    RejectedExecutionHandler handler) //拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略,共有四种。
          用给定的初始参数创建新的 ThreadPoolExecutor。 

常用的几个阻塞队列:

LinkedBlockingQueue	//链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE,也可以指定大小
ArrayBlockingQueue		//数组阻塞队列,底层数据结构是数组,需要指定队列的大小
SynchronousQueue		//同步队列,内部容量为0,第个put操作必须等待一个take操作,反之亦然
DelayQueue				//延迟队列,该队列中的元素只有当其指定的延迟时间到了,都能够从队列中区取到该元素。

拒绝处理策略:

ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序(如果再次失败,重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

2.2 ThreadPoolExecutor 的策略

线程池本身有一个调度线程,这个线程就是用于管理布控整个线程池里的各种任务和事务,例如创建线程、销毁线程、任务队列管理、线程队列管理等等。

帮线程池也有自己的状态。ThreadPoolExecutor 类中定义了一个volatile int 变量runState 来表示线程池的状态,分别为 RUNNING, SHUTDOWN,STOP, TIDYING, TERMINATED。

  • 线程池创建后处于RUNNING状态
  • 调用shutdown() 方法后处于SHUTDOWN状态,线程池不能接受新的任务,清除一些空闲worker,会等待阻塞队列的任务完成。
  • 调用shutdownNow() 方法后处于STOP状态,线程池不能接受新的任务,中断所有线程,阻塞队列中没有被执行的任务全部丢弃。此时,poolsize = 0, 阻塞队列的size 也为0。
  • 当所有的任务已终止,ctl记录的“任务数量”为0,线程池会变为TIDYING状态,接着会执行 terminated() 函数。(ctl是ThreadPoolExecutor中的一个控制状态的属性,初级蝎毒上一个AtomicInteger类型的变量。)
  • 线程池处在TIDYING状态时,执行完terminated() 方法之后,就会由TIDYING -> TERMINATED, 线程池被设置为TERMINATED状态。

2.3 线程池主要的任务处理流程

处理任务的核心方法是execute(Runnable command)

方法中二次检查了线程池的状态,原因是在多线程的环境下,线程池的状态是时刻心生变化的。很有可能刚获取线程池状态后线程池状态就改变了。判断是否将command 加入workqueue是线程池之前的状态。倘若没有二次检查,万一线程池处于非RUNNING状态(在多线程环境下很有可能发生),那么command永远不会执行。

处理流程

1.线程总数量 < corePoolSize,无论线程是否空闲,都会创建一个核心线程执行任务(让核心线程数量快速大宝以corePoolSize,在核心线程数量 < corePoolSize时)。注意,这一步需要获得全局锁。

2.线程总数量 >= core PoolSize 时,新来的线程任务会进入任务队列中等待,然后空闲的核心线程会依次去缓存队列中取任务来执行(体现了线程复用)。

3.当缓存队列满了,说明这个时候任务已经多到爆棚,需要一些“临时工”来执行这些任务了。于是会创建非核心线程去执行这个任务。注意,这一步需要获得全局锁。

4.缓存队列满了,且总线程数达到了maximumPoolSize, 则会采取上面提到的拒绝策略进行处理。

 

2.4 ThreadPoolExecutor是如何做到线程复用的?

ThreadPoolExecutor在创建线程时,会将线程封装成工作线程worker,并放入工作线程组中,然后这个worker反复从阻塞队列中拿任务去执行。

addWorker方法是在上面提到的 execute 方法里面调用的,其上半部分主要是判断线程数量是否超出阈值,超过了就返回false。下半部分创建worker 对象,并初始化一个Thread对象,然后启动这个线程对象。

worker类中,实现了Runnable接口,所以Worder 也是一个线程任务。在构造方法中,创建了一个线程,线程的任务就是自己。故addWorker 方法调用addWorker 方法源码下半部分中的第4步t.start,会触发Worker类的run方法被JVM调用。

首先去执行创建这个worker时就有的任务,当执行完这个任务后,worder 的生命周期并没有结束,在while循环中,worker会不断地调用getTask方法从阻塞队列中获取任务然后调用task.run()执行任务,从而达到复用线程的目的。只要getTask方法不返回null,此线程就不会退出。

当然,核心线程池中创建的线程想要拿到阻塞队列中的任务,先要判断线程池的状态,如果是STOP或者TERMINATED,返回null。

getTask方法中,核心线程会一直卡在workQueue.take方法,被阻塞并挂起,不会占用CPU资源,直到拿到Runnable然后返回(当然如果allowCoreThreadTimeOut设置为true,那么核心线程就会去调用poll方法,因为poll可能会返回null,所以这时候核心线程满足超时条件也会被销毁)。

非核心线程会workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),如果 超时还没有拿到,一次循环判断compareAndDecrementWorkerCount就会返回null,Worker对象的run()方法循环体的判断为null,任务结束,然后线程被系统回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值