ThreadPool和ThreadLocal
ThreadPool是什么?
ThreadPool是指线程池!提到线程池大家肯定会想到Callable吗?线程的第三种实现,Callable接口与Runnable不同之处在于Callable有返回值.线程池相当于一个容器,具体的实现包括如下:
一般通过Executor框架的工具类Executors来创建ThreadPoolExecutor,可以创建三种类型的ThreadPoolExecutor:
1. FixedThreadPool
2. SingleThreadPool
3. CachedThreadPool
FixedThreadPool
FixedThreadPool被称为可重用固定线程数的线程池。源代码:
public static ExecutorServicenewFixedThreadPool(int nThreads) {
return newThreadPoolExecutor(nThreads, nThreads,
0L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue<Runnable>());
}
nThreads:FixedThreadPool指定参数
当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后的多余的线程将被终止。这里把keepAliveTime设置为0L,意味多余的线程将会被立即终止。
FixedThreadPool的execute()方法的运行示意图如图
说明:
1. 如果当前运行的线程数小于corePoolSize,则创建新线程。
2. 在完成预热后,将任务加入LinkedBlockingQueue。
3. 线程执行完1中的任务后,将会在循环中反复从LinkedBlockingQueue获取任务来执行。
FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响。
1. 当线程池中的线程数达到corePoolSize后,新任务将在LinkedBlockingQueue中等待,因此线程池中线程数不会超过corePoolSize。
2. 由于1,使用无界队列时maximumPoolSize将是一个无效参数。
3. 由于1和2,使用无界队列时keepAliveTime将是一个无效参数。
4. 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shoutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution()方法)。
SingleThreadPool
SingleThreadPool是使用单个worker线程的Executor。源代码实现:
public static ExecutorServicenewSingleThreadExecutor() {
return newFinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1,1,
0L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue<Runnable>()));
}
和FixedThreadPool基本一样,只是将初始值设置为1。
CachedThreadPool
CachedThreadPool是一个会根据需要来创建新线程的线程池。源代码实现:
public staticExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
newSynchronousQueue<Runnable>());
}
corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE,即maximumPool是无界的,keepAliveTime为60L,线程最大时间为60秒。
因为maximumPoolSize为无界的,如果主线程提交任务的速度高于CachedThreadPool中线程处理任务的速度,CachedThreadPool会不断创建线程,极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
CachedThreadPool的execute()方法的运行示意图如图:
说明:
1. 首先执行SynchronousQueue.offer(Runnabletask)。如果当前maximumPoolSize中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程吧任务交给空闲线程执行,execute()方法执行完成;否则执行下面的步骤2。
2. 当初始maximumPool为空,或者maximumPool中没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)操作,这种情况下步骤1将失败,此时CachedThreadPool将会创建一个新线程来执行任务,execute()方法执行完成。
3. 在步骤2中创建新的线程将任务执行完成后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)操作,这个poll操作将会让线程最多在SynchronousQueue中等待60秒。如果60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务;否则这个空闲线程将终止,因此长时间空闲的CachedThreadPool不会使用任何资源。
ScheduledThreadPoolExecutor
主要用于在给定的延迟之后执行任务,或者定期执行任务,比Timer更强大,更灵活,基于DealyQueue。
执行主要分为两大部分
1) scheduleAtFixedRate()方法或者scheduleWithFixedDelay()会向ScheduledThreadPoolExecutor中添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask
2) 线程池中的线程从DealyQueue中能够获取ScheduledFutureTask,然后执行任务。
具体实现
ScheduledFutureTask主要包含3个成员变量
1) long time,表示这个恩物将要被执行的具体时间。
2) long sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。
3) long period,表示任务执行间隔周期。
DealyQueue封装了一个priorityQueue,这个priorityQueue会对ScheduledFutureTask进行排序。排序时,time小的排在前面。如果time相同,则比较sequenceNumber,小的排在前面,则先加入的先执行。
如图:
1) 线程1从队列中获取到已到期的任务,是指任务的时间于等于当前时间。
2) 线程1执行任务。
3) 线程1修改任务的time变量为下次将要执行的时间。
4) 线程1把修改time之后任务重新放回队列。
步骤1获取任务过程图:
1. 获取lock。
2. 获取周期任务
2.1 如果priorityQueue为空,当前线程到Condition中等待;否则执行下面的2.2;
如果priorityQueue的头元素的time时间比当前时间大,到Condition中等待到2.2 time时间;否则执行下面的2.3;
2.3 获取priorityQueue的头元素(2.3.1);如果priorityQueue不为空,则唤醒在Condition中等待的所有线程(2.3.2)。
3. 释放lock。
ScheduledThreadPoolExecutor在一个循环中执行步骤2,直到线程从priorityQueue获取到一个元素后,才会退出无线循环。
步骤4添加任务过程图
1. 获取lock。
2. 添加任务。
2.1 向priorityQueue添加任务。
2.2 如果在上面2.1中添加的任务是priorityQueue的头元素,唤醒在Condition中等待的所有线程。
3. 释放lock。
ThreadLocal是什么?
ThreadLoacl相当于一个容器,存放本地线程变量,意思是说在多线程环境下,线程自己操作的变量不需要和其他线程进行交互,name就可以将这类变量存放在本地线程中,还有一个点不知道大家能注意到吗?线程是在栈上开的内存,本地线程的内存也在线程中,这意味着线程执行完毕,本地线程内的数据就消失了,可以有效地降低JVM的垃圾回收时间,当然最重要的一点是拿来做多线程下数据安全的保障.
总结:线程池部分借鉴了我大学同学也是我的好老师张亮给我的笔记,希望对大家有用,不足或错误的地方希望支出,Thanks♪(・ω・)ノ!