- 线程池
线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
一个线程池包括以下四个基本组成部分:
- 线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
- 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
- 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
假设一个服务器完成一项任务所需时间为: T1 创建线程时间, T2 在线程中执行任务的时间, T3 销毁线程时间。如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。 线程池技术正是关注如何缩短或调整 T1,T3 时间的技术,从而提高服务器程序性能的。它把 T1 , T3 分别 安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时, 不会有 T1 , T3 的开销了。 线程池不仅调整 T1,T3 产生的时间段,而且它还显著减少了创建线程的数目。
线程池的优点:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。 如果:T1+ T3 远大于 T2,则可以采用线程池,以提高服务器性能。线程池技术正是关注如何缩短或调整 T1,T3时间的技术,从而提高服务器程序性能的。它把 T1,T3 分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有 T1,T3 的开销了。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
- 常见的线程池类型
1、SingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务。即创建一个单线程的线程池。这个线程池只有一个核心线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2、FixedThreadExecutor(n)
固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。即使用的Thread对象的数量是有限的,如果提交的任务数量大于限制的最大线程数,那么这些任务讲排队,然后当有一个线程的任务结束之后,将会根据调度策略继续等待执行下一个任务。
3、CacheThreadExecutor (推荐使用)
可缓存线程池, 当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是 60 秒无执 行)的线程,当有任务来时,又智能的添加新线程来执行。该线程池比较适合没有固定大小并且比较快速就能完成的小任务,它将为每个任务创建一个线程。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4、ScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程。即核心线程池固定,大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
- 线程池源码分析
线程池类继承关系:
线程池类继承关系图
Executor接口类
public interface Executor {
/*** Executes the given command at some time in the future. The command * may execute
in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the
{@code Executor} implementation. ** @param command the runnable task * @throws
RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */
void execute(Runnable command);
}
Executor 接口是线程池类的总接口,它非常简单,就一个 void execute(Runnable command) 方法,代表提交一个任务。 Executor 这个接口只有提交任务的功能,如果想要更丰富的功能,比如知道执行结果、知道当前线程池有多少个线程活着、已经完成了多少任务等等,这些都是这个接口 的不足的地方。接下来介绍的是继承自 Executor 接口的 ExecutorService 接口,这个接口提供了比较丰富的功能,也是最常使用到的接口。
ExecutorService
public interface ExecutorService extends Executor {
// 关闭线程池,已提交的任务继续执行,不接受继续提交新任务
void shutdown();
// 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务
// 它和前面的方法相比,加了一个单词“now”,区别在于它会去停止当前正在进行的任务
List<Runnable> shutdownNow();
// 线程池是否已关闭
boolean isShutdown();
// 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true
// 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true
boolean isTerminated();
// 等待所有任务完成,并设置超时时间
// 我们这么理解,实际应用中是,先调用 shutdown 或 shutdownNow,
// 然后再调这个方法等待所有的线程真正地完成,返回值意味着有没有超时
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
// 提交一个 Callable 任务 <T>
Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务,第二个参数将会放到 Future 中,作为返回值,
// 因为 Runnable 的 run 方法本身并不返回任何东西 <T>
Future<T> submit(Runnable task, T result);
// 提交一个 Runnable 任务
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
一个简单的线程池主要就是这些功能,能提交任务,能获取结果,能关闭线程池,这也是为什么我们经常用这个接口的原因。此外, AbstractExecutorService 抽象类派生自 ExecutorService 接口,然后在其基础上实现了几个实用的方 法,这些方法提供给子类进行调用。 这个抽象类实现了 ExecutorService 中的 submit 方法, newTaskFor 方法用于将任务包装成 FutureTask 。定义于最上层接口 Executor中的 void execute(Runnable command) 由于不需要获取结果,不会进行 FutureTask 的包装。AbstractExecutorService的源码如下:
public abstract class AbstractExecutorService implements ExecutorService {
// RunnableFuture 是用于获取执行结果的,我们常用它的子类 FutureTask
// 下面两个 newTaskFor 方法用于将我们的任务包装成 FutureTask 提交到线程池中执行
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
// 提交任务
public Future<?> submit(Runnable task) {
if (task == null)
throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 2. 交给执行器执行,execute 方法由具体的子类来实现
// 前面也说了,FutureTask 间接实现了Runnable 接口。
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task, result);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null)
throw new NullPointerException();
// 1. 将任务包装成
FutureTask RunnableFuture<T> ftask = newTaskFor(task);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
}
这个抽象类实现和包装了一些基本的方法,但 submit 等方法,它们都没有真正开启线程来执行任务,它们都只是在方法内部调用了 execute 方法,所以最重要的 execute(Runnable runnable) 方法还没实现,因此就有了 ThreadPoolExecutor 类了。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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
构造方法中出现的属性详解:
corePoolSize
线程池中的核心线程数。
maximumPoolSize
最大线程数,线程池允许创建的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize ;当阻塞队列是无界队列 , 则maximumPoolSize则不起作用 , 因为无法提交至核心线程池的线程会一直持续地放入 workQueue。
workQueue
用来保存等待被执行的任务的阻塞队列 . 在 JDK中提供了如下阻塞队列:
(1) ArrayBlockingQueue :基于数组结构的有界阻塞队列,按 FIFO 排序任务;
(2) LinkedBlockingQuene:基于链表结构的阻塞队列,按 FIFO 排序任务,吞吐量通常要高于ArrayBlockingQuene;
(3) SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene ;
(4) priorityBlockingQuene:具有优先级的无界阻塞队列;
keepAliveTime
空闲线程的保活时间,如果某线程的空闲时间超过这个值都没有任务给它做,那么可以被关闭了。注意这个值并不会对所有线程起作用,如果线程池中的线程数少于等于核心线程数 corePoolSize ,那么这些线程不会因为空闲太长时间而被关闭,当然,也可以通过调用
allowCoreThreadTimeOut(true) 使核心线程数内的线程也可以被回收;默认情况下,该参数只在线程数大于 corePoolSize 时才有用 , 超过这个时间的空闲线程将被终止。
unit
keepAliveTime 的单位
threadFactory
用于生成线程,一般我们可以用默认的就可以了。通常,我们可以通过它将我们的线程的名字设置得比较可读一些,如 Message-Thread-1 , Message-Thread-2 类似这样。
handler
线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4 种策略:
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
线程池中的各个状态和状态变化的转换过程:
- RUNNING:这是正常的状态,接受新的任务,处理等待队列中的任务。
- SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
- STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
- TIDYING:所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
- TERMINATED:terminated() 方法结束后,线程池的状态就会变成这个。
- 线程的状态
各个状态的转换过程有以下几种:
- RUNNING ->SHUTDOWN:当调用了 shutdown() 后,会发生这个状态转换,这也是最重要的。
- (RUNNING or SHUTDOWN) -> STOP:当调用 shutdownNow() 后,会发生这个状态转换,这下要清楚 shutDown() 和shutDownNow() 的区别了。
- SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING 。
- STOP -> TIDYING:当任务队列清空后,发生这个转换 TIDYING -> TERMINATED:当 terminated() 方法结束后。
另外,ThreadPoolExecutor还有一个内部类 Worker ,因为 Doug Lea 把线程池中的线程包装成了一个个 Worker ,就是线程池中做任务的线程。所以我们知道任务是 Runnable (内部叫 task 或 command),线程是 Worker 。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
/** * This class will never be serialized, but we provide a * serialVersionUID to
suppress a javac warning. */
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
// 这个是真正的线程,任务靠你啦
final Thread thread;
/** Initial task to run. Possibly null. */
// 前面说了,这里的 Runnable 是任务。为什么叫 firstTask?因为在创建线程的时候,如果同时指定
了
// 这个线程起来以后需要执行的第一个任务,那么第一个任务就是存放在这里的(线程可不止执行这一 个
任务)// 当然了,也可以为 null,这样线程起来了,自己到任务队列(BlockingQueue)中取任务
(getTask 方法)就行了 Runnable firstTask;
/** Per-thread task counter */
// 用于存放此线程完全的任务数,注意了,这里用了 volatile,保证可见性 volatile long completedTasks;
/** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */
// Worker 只有这一个构造方法,传入 firstTask,也可以传 null
Worker(Runnable firstTask) {
setState(-1);
// inhibit interrupts until runWorker this.firstTask = firstTask;
// 调用 ThreadFactory 来创建一个新的线程,这里创建的线程到时候用来执行任务
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
// 这里调用了外部类的 runWorker 方法
public void run() {
runWorker(this);
}
// Lock methods //
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() {
acquire(1);
}
public boolean tryLock() {
return tryAcquire(1);
}
public void unlock() {
release(1);
}
public boolean isLocked() {
return isHeldExclusively();
}
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
- 线程的执行
线程的执行主要依赖于ThreadPoolExecutor 的 execute 方法,该方法最终继承于Excutor接口于 execute 方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 前面说的那个表示 "线程池状态" 和 "线程数" 的整数
int c = ctl.get();
// 如果当前线程数少于核心线程数,那么直接添加一个 worker 来执行任务,
// 创建一个新的线程,并把当前任务 command 作为这个线程的第一个任务(firstTask)
if (workerCountOf(c) < corePoolSize) {
// 添加任务成功,那么就结束了。提交任务嘛,线程池已经接受了这个任务,这个方法也就可以 返回了
// 至于执行的结果,到时候会包装到 FutureTask 中。
// 这里的true代表当前线程数小于corePoolSize,表示以corePoolSize为线程数界限
if (addWorker(command, true))
return;
c = ctl.get();
}
// 到这里说明,要么当前线程数大于等于核心线程数,要么刚刚 addWorker 失败了
// 如果线程池处于 RUNNING 状态,把这个任务添加到任务队列 workQueue 中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 如果线程池已不处于 RUNNING 状态,那么移除已经入队的这个任务,并且执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果 workQueue 队列满了,那么进入到这个分支
// 这里的false代表当前线程数大于corePoolSize,表示以 maximumPoolSize 为界创建新的 worker
// 如果失败,说明当前线程数已经达到 maximumPoolSize,执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
执行的总体流程如下图所示:
线程的执行流程
- 任务缓存队列及排队策略
任务缓存队列,即 workQueue ,它用来存放等待执行的任务。 workQueue 的类型为 BlockingQueue, 通常可以取下面三种类型:
- ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
- LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
- synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
- 任务拒绝策略
上述execute(Runnable command) 方法中,有两种情况会调用 reject(command) 来处理任务,因为按照正常的流程,线程池此时不能接受这个任务,所以需要执行我们的拒绝策略。
final void reject(Runnable command) {
// 执行拒绝策略
handler.rejectedExecution(command, this);
}
此处的 handler 我们需要在构造线程池的时候就传入这个参数,它是 RejectedExecutionHandler 的实例。RejectedExecutionHandler 在 ThreadPoolExecutor 中有四个已经定义好的实现类可供我们直接使用,当然,我们也可以实现自己的策略,不过一般也没有必要。这四种拒绝策略分别是:
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
// 只要线程池没有被关闭,那么由提交任务的线程自己来执行这个任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
// 不管怎样,直接抛出 RejectedExecutionException 异常
// 这个是默认的策略,如果我们构造线程池的时候不传相应的 handler 的话,那就会指定使用这个
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
// 不做任何处理,直接忽略掉这个任务
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
// 这个相对霸道一点,如果线程池没有被关闭的话,
// 把队列队头的任务(也就是等待了最长时间的)直接扔掉,然后提交这个任务到等待队列中
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
- 线程池的关闭
ThreadPoolExecutor 提供了两个方法,用于线程池的关闭,分别是 shutdown() 和 shutdownNow() ,其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。
- 线程池容量的动态调整
ThreadPoolExecutor 提供了动态调整线程池容量大小的方法: setCorePoolSize() 和 setMaximumPoolSize(),
- setCorePoolSize:设置核心池大小。
- setMaximumPoolSize:设置线程池最大能创建的线程数目大小。
当上述参数从小变大时, ThreadPoolExecutor 进行线程赋值,还可能立即创建新的线程来执行任务。
- 方便的创建和使用:Executors工具类
常会使用 Executors 这个工具类来快速构造一个线程池,对于初学者而言,这种工具类是很有用的,开发者不需要关注太多的细节,只要知道自己需要一个线程池,仅仅提供必需的参数就可以了,其他参数都采用作者提供的默认值。
Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法,类似于集合中的Collections类的功能。
Executors工具类提供的创建线程池方法
方法源码为:
//创建可缓存的线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
//创建固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
//创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
//创建大小无限的线程池
public static ExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPool(corePoolSize,
Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
Executors工具类用法示例:
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
public class ThreadPoolExecutorDemo{
public static void main(String[]args){
//线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,
//而不用每次新建线程。
ExecutorService cachedThreadPool= Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
final int index=i;
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable(){
public void run(){
System.out.println(index);
}
});
}
}
}
- forkjoin
forkjoin 可以让我们不去了解诸如Thread 、 Runnable 等相关的知识,只要遵循 forkjoin 的开发模式,就可以写出很好的多线程并
发程序。forkjoin采用的是分而治之。分而治之思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。分而6 治之的策略是:对于一个规模为 n 的问题,若该问题可以容易地解决(比如说规模n 较小)则直接解决,否则将其分解为 m 个规模较小的子问题, 这些子问题互相 独立且与原问题形式相同 * *( 子问题相互之间有联系就会变为动态规范算法 )** ,递归地解这些子问题,然后将各子问题的解合并得到原问题的解,这种算法设计策略叫做分治法。
ForkJoin框架其实就是一个线程池ExecutorService的实现,通过工作窃取(work-stealing)算法,获取其他线程中未完成的任务来执行。可以充分利用机器的多处理器优势,利用空闲的线程去并行快速完成一个可拆分为小任务的大任务,类似于分治算法。ForkJoin的目标,就是利用所有可用的处理能力来提高程序的响应和性能。
forkjoin类继承关系
forkjoin分治法思想原理图
- Fork-Join使用
Fork-Join
使用两个类来完成以上两件事情:
ForkJoinTask
和
ForkJoinPool
。我们要使用
ForkJoin
框
架,必须首先创建一个
ForkJoin
任务。它提供在任务中执行
fork
和
join
的操作机制,通常我们不直接继
承
ForkjoinTask
类,只需要直接继承其子类。
- RecursiveAction,用于没有返回结果的任务。
- RecursiveTask,用于有返回值的任务。
task 要通过 ForkJoinPool 来执行,使用 submit 或 invoke 提交,两者的区别是: invoke 是同步执行,调用之后需要等待任务完成,才能执行后面的代码;submit 是异步执行。 join() 和 get 方法当任务完成的时候返回计算结果。调用get/join方法的时候会阻塞。 在我们自己实现的 compute 方法里,首先需要判断任务是否足够小,如果足够小就直接执行任务。如果 不足够小,就必须分割成两个子任务,每个子任务在调用 invokeAll 方法时,又会进入 compute 方法, 看看当前子任务是否需要继续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。使 用 join 方法会等待子任务执行完并得到其结果。
Fork-Join使用示例:
import java.util.concurrent.RecursiveTask;
public class ForkJoinWork extends RecursiveTask<Long> {
private Long start;//起始值
private Long end;//结束值
public static final Long critical = 100000L;//临界值
public ForkJoinWork(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
//判断是否是拆分完毕
Long lenth = end - start;
if(lenth<=critical){
//如果拆分完毕就相加
Long sum = 0L;
for (Long i = start;i<=end;i++){
sum += i;
}
return sum;
}else {
//没有拆分完毕就开始拆分
Long middle = (end + start)/2;//计算的两个值的中间值
ForkJoinWork right = new ForkJoinWork(start,middle);
right.fork();//拆分,并压入线程队列
ForkJoinWork left = new ForkJoinWork(middle+1,end);
left.fork();//拆分,并压入线程队列
//合并
return right.join() + left.join();
}
}
}
public class ForkJoinWorkDemo {
@Test
public void test() {
//ForkJoin实现
long l = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();//实现ForkJoin 就必须有ForkJoinPool的支持
ForkJoinTask<Long> task = new ForkJoinWork(0L,10000000000L);//参数为起始值与结束值
Long invoke = forkJoinPool.invoke(task);
long l1 = System.currentTimeMillis();
System.out.println("invoke = " + invoke+" time: " + (l1-l));
//invoke = -5340232216128654848 time: 76474
}
@Test
public void test2(){
//普通线程实现
Long x = 0L;
Long y = 10000000000L;
long l = System.currentTimeMillis();
for (Long i = 0L; i <= y; i++) {
x+=i;
}
long l1 = System.currentTimeMillis();
System.out.println("invoke = " + x+" time: " + (l1-l));
//invoke = -5340232216128654848 time: 160939
}
@Test
public void test3(){
//Java 8 并行流的实现
long l = System.currentTimeMillis();
long reduce = LongStream.rangeClosed(0, 10000000000L).parallel().reduce(0, Long::sum);
long l1 = System.currentTimeMillis();
System.out.println("invoke = " + reduce+" time: " + (l1-l));
//invoke = -5340232216128654848 time: 15531
}
}