Java线程池和线程相关类
前言
参考项目:https://github.com/wodongx123/ThreadPoolDemo
1.线程池
Java Pool(Java 池)是为了减少资源创建、初始化的系统开销而采取的一种实现模式。
线程池维护多个线程,可以有效控制系统中并发的线程数量。
- 线程的创建和销毁的开销比较高,而线程池会在系统启动时即时创建好几个线程。
- 程序中需要大量创建生存时间很短的线程时,可以使用线程池。
- 线程池可以有效的控制程序内的线程数量。
1.1 线程池的种类
newCachedThreadPool()
创建一个有缓存功能的线程池,根据系统需要创建线程,这些线程会存放在缓存中。
- 放入缓存的线程拥有复用性,可以一遍又一遍的拿出来跑程序。
- 放入缓存的线程,在经过一段时间后如果没有用到(默认60s),就会自动销毁并且移出缓存。所以CachedThreadPool挂机长时间后,它不会占用系统资源。
- 如果缓存内没有新线程,会自动创建一个新的。
- 适合用来处理时间较短的异步任务。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newFixedThreadPool(int nThreads)
创建一个可重用的,具有固定线程数的线程池。
newSingleThreadExecutor相当于newFixedThreadPool(1).
- 创建指定数量的线程在线程池内待机。
- 重复完成任务。
- 如果在完成任务的中途出错,会创建一个新的线程来代替它继续工作。
- 线程不会随着时间的推移而被清除(也就是说除非被shutdown,不然这种线程池在创建的时候就会一直占用系统资源)。
- 适合处理长期任务。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newScheduledThreadPool(int corePoolSize)
创建有指定线程数的线程池,可以在延迟指定时间后执行任务或者按周期执行任务,corePoolSize表示池中保存的线程数,不会随着空闲而被移除。
newSingleThreadScheduledExecutor()相当于newScheduledThreadPool(1)。
- 通过ScheduledExecutorService创建。
- 创建指定数量的线程在线程池内待机。
- 重复的根据设定的延迟时间和周期时间完成任务。
- 如果在完成任务的中途出错,会创建一个新的线程来代替它继续工作。
- 线程不会因为空闲而被移除。
- 适合处理周期性任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledFuture<?> schedule(Runnable command,
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);
- schedule(): 延迟指定的时间delay后开始任务command。
- scheduledAtFixedRate():延迟指定的时间initialDelay后,开始任务。从开始任务时开始计时,每经过period后重新开始任务(周期性执行);如果任务的执行时长taskTime大于period的话,每经过taskTime后重新开始任务。
- scheduleWithFixedDelay():延迟指定的时间initialDelay后,开始任务。从任务结束时开始计时,每经过deley时长后重新开始任务。
newWorkStealingPool(int parallelism)
创建持有足够的线程池来支持给定的并行级别,使用多个队列来减少竞争。
stealing翻译是窃取,表示线程再完成自己的任务后会去主动的找任务来做。
newWorkStealingPool()根据CPU的核心数来设置并行等级,四核就是4,六核就是6。
- JDK1.8以上才支持的功能。
- 创建的线程池是后台线程,当主线程结束后就会立刻销毁。
- 实际上的线程数不确定,会根据需求增加或减少,但是保证支持所需要的并行等级。
- 不保证提交的任务按顺序执行(比如说任务按12345提交给线程池,但是线程池不一定会按12345来处理,有可能是54321,也有可能是别的顺序)。
- 适合执行不需要按顺序执行的复杂任务。
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool
(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
1.2 ThreadPoolExecutor
上面的几个线程池除了newWorkStealingPool实际上到最后都是调用了ThreadPoolExecutor(),只是使用的参数不同。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数 | 类型 | 描述 |
corePoolSize | int | 核心线程池大小,也就是常驻线程的数量 |
maximumPoolSize | int | 最大线程池大小,所有线程(常驻线程+缓存线程)加起来的数量上限 |
keepAliveTime | long | 线程保活时间,缓存线程最长可以存在的时间,超过这个时间就被移除,为0表示无限时间 |
unit | TimeUnit | 时间单元,对上一个参数的时间进行单位描述,比如xTimeUnit.SECONDS为秒 |
workQueue | BlockingQueue | 任务队列 |
threadFactory | ThreadFactory | 线程工厂,用于创建新线程的,是一个接口,内部就一个new方法 |
handler | RejectedExecutionHandler | 拒绝策略 |
1.3 ForkJoinPool
Java7开始额外增加的新的线程池,它支持将任务拆分成小任务,最后再合并成大任务模式。在Java8增加了通用池的功能。
- 直接使用ForkJoinPool实例化。通用池使用commonPool调用,但是通用池不受shutdown影响。
- 其中的线程是后台线程,如果主线程结束,会自动销毁。
- 使用RecursiveAction(有返回值)和RecursiveTask来执行任务,两者均是extends,通过compute()方法写具体任务内容。
- fork()会安排一个线程来执行指定的子任务。
- join()会返回指定子任务的结果(如果是RecursiveAction就没有这个方法)。
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
2. 线程相关类
ThreadLocal
从另一个角度解决多线程冲突的类,不同于普通同步机制的加锁访问方式,ThreadLocal会将数据复制多份让每个线程都能访问数据。
- 复制的数据不会影响到主线程的数据。
- 主线程的数据不会影响到子线程的数据。
- 通过initialValue设置默认值,没有重写的话就是null
- 通过set和get的方法设置,获取值。
InheritableThreadLocal
和ThreadLocal大部分一样,但是这种情况下主线程的值可以传给子线程。
- 子线程的数据还是无法影响主线程。
参考材料
疯狂JAVA讲义(第5版)
p768 - p779
线程池- 百度百科
https://baike.baidu.com/item/Java%20Pool/4715004?fr=aladdin#4
线程池的种类,区别和使用场景 - CNblog
https://www.cnblogs.com/sachen/p/7401959.html
可缓存线程池newCachedThreadPool - 简书
https://www.jianshu.com/p/4406c2aeff0f