为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用。线程池中,总有那么几个活跃线程。当你需要使用线程时,可以从池子中随便拿一个空闲线程,当完成工作时,并不急着关闭线程,而是将整个线程退回到池子,方便其他人使用。

1.1 JDK对线程池的支持

JDK提供一套Executor框架,帮助开发人员更好的有效的控制多线程。

其中ThreadPoolExecutor表示一个线程池。Executors类则扮演这线程池工厂的角色。通过Executors可以取得一个拥有特定功能的线程池。ThreadPoolExecutor类实现了Executor接口,因此通过这个接口,任何Runnable的对象都可以被ThreadPoolExecutor线程池调度。

Executor框架提供了各种类型的线程池,主要有以下工厂方法:

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
  • newFixedThreadPool:返回一个固定数量的线程池。当一个新任务提交时,如果有空闲线程,则执行。否则新任务暂存在一个任务队列中,待有空闲时,便处理在任务队列中的任务。
  • newSingleThreadExecutor:返回一个线程的线程池。当多余一个新任务提交时,会暂存在一个任务队列中,待有空闲时,按先入先出的顺序处理在任务队列中的任务。
  • newCachedThreadPool:返回一个可根据实际情况调整线程数量的线程池,线程数量不确定,若有空闲,则会有限复用线程。否则创建新线程处理任务。所有线程在当前任务执行完后,将返回线程池待复用。
  • newSingleThreadScheduledExecutor:返回一个ScheduledExecutorService对象,线程池大小为1。ScheduledExecutorService在Executor接口之上扩展了在给定时间执行某任务的功能。如果在某个固定的延时之后执行,或周期性执行某个任务。可以用这个工厂。
  • newScheduledThreadPool,返回一个ScheduledExecutorService对象,但该线程可以指定线程数量。

1.2 核心线程池的内部实现

对于核心的几个线程池,无论是newFixedThreadPool()方法、newSingleThreadExecutor()还是newCachedThreadPool()方法,其内部实现均使用了ThreadPoolExecutor实现。下面给出三个线程池的实现方式:

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

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

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

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

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

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

由以上线程池实现代码可以看到,它们都只是ThreadPoolExecutor类的封装。ThreadPoolExecutor最重要的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize:指定了线程池中的线程数量。
  • maximumPoolSize:指定了线程池中的最大线程数量。
  • keepAliveTime:当前线程池数量超过corePoolSize时,多余的空闲线程的存活时间,即多次时间内会被销毁。
  • unit:keepAliveTime的单位。
  • workQueue:任务队列,被提交但尚未被执行的任务。
  • threadFactory:线程工厂,用于创建线程,一般用默认的即可。
  • handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。

参数workQueue指被提交但未执行的任务队列,它是一个BlockingQueue接口的对象,仅用于存放Runnable对象。根据队列功能分类,在ThreadPoolExecutor的构造函数中可使用一下几种BlockingQueue。

  1. 直接提交的队列:该功能由synchronousQueue对象提供,synchronousQueue对象是一个特殊的BlockingQueue。synchronousQueue没有容量,每一个插入操作都要等待一个响应的删除操作,反之每一个删除操作都要等待对应的插入操作。如果使用synchronousQueue,提交的任务不会被真实的保存,而总是将新任务提交给线程执行,如果没有空闲线程,则尝试创建线程,如果线程数量已经达到了最大值,则执行拒绝策略,因此,使用synchronousQueue队列,通常要设置很大的maximumPoolSize值,否则很容易执行拒绝策略。
  2. 有界的任务队列:有界任务队列可以使用ArrayBlockingQueue实现。ArrayBlockingQueue构造函数必须带有一个容量参数,表示队列的最大容量。
public ArrayBlockingQueue(int capacity)。

public ArrayBlockingQueue(int capacity)。

当使用有界任务队列时,若有新任务需要执行时,如果线程池的实际线程数量小于corePoolSize,则会优先创建线程。若大于corePoolSize,则会将新任务加入等待队列。若等待队列已满,无法加入,则在总线程数不大于maximumPoolSize的前提下,创建新的线程执行任务。若大于maximumPoolSize,则执行拒绝策略。可见有界队列仅当在任务队列装满后,才可能将线程数量提升到corePoolSize以上,换言之,除非系统非常繁忙,否则确保核心线程数维持在corePoolSize。
3. 无界的任务队列:无界队列可以通过LinkedBlockingQueue类实现。与有界队列相比,除非系统资源耗尽,无界队列的任务队列不存在任务入队失败的情况。若有新任务需要执行时,如果线程池的实际线程数量小于corePoolSize,则会优先创建线程执行。但当系统的线程数量达到corePoolSize后就不再创建了,这里和有界任务队列是有明显区别的。若后续还有新任务加入,而又没有空闲线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,知道耗尽系统内存。
4. 优先任务队列:带有优先级别的队列,它通过PriorityBlokingQueue实现,可以控制任务执行的优先顺序。它是一个特殊的无界队列。无论是ArrayBlockingQueue还是LinkedBlockingQueue实现的队列,都是按照先进先出的算法处理任务,而PriorityBlokingQueue根据任务自身优先级顺序先后执行,在确保系统性能同时,也能很好的质量保证(总是确保高优先级的任务优先执行)。

newFixedThreadPool()方法的实现,它返回一个corePoolSize和maximumPoolSize一样的,并使用了LinkedBlockingQueue任务队列(无界队列)的线程池。当任务提交非常频繁时,该队列可能迅速膨胀,从而系统资源耗尽。

newSingleThreadExecutor()返回单线程线程池,是newFixedThreadPool()方法的退化,只是简单的将线程池数量设置为1。

newCachedThreadPool()方法返回corePoolSize为0而maximumPoolSize无穷大的线程池,这意味着没有任务的时候线程池内没有现场,而当任务提交时,该线程池使用空闲线程执行任务,若无空闲则将任务加入SynchronousQueue队列,而SynchronousQueue队列是直接提交队列,它总是破事线程池增加新的线程来执行任务。当任务执行完后由于corePoolSize为0,因此空闲线程在指定时间内(60s)被回收。对于newCachedThreadPool(),如果有大量任务提交,而任务又不那么快执行时,那么系统变回开启等量的线程处理,这样做法可能会很快耗尽系统的资源,因为它会增加无穷大数量的线程。

1.3 拒绝策略

当任务数量超过系统实际承载能力时,需要拒绝策略机制解决这个问题。JDK内置的拒绝策略如下:

  1. AbortPolicy : 直接抛出异常,阻止系统正常运行。
  2. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
  3. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  4. DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了RejectedExecutionHandler接口,若以上策略仍无法满足实际需要,完全可以自己扩展RejectedExecutionHandler接口。RejectedExecutionHandler的定义如下:

public interface RejectedExecutionHandler() {
  public void rejectedExecution(Runnable r, ThreadPoolExecutor  executor);
});

public interface RejectedExecutionHandler() {
  public void rejectedExecution(Runnable r, ThreadPoolExecutor  executor);
});

下面代码演示自定义线程池和拒绝策略的使用:

public class RejectThreadPoolDemo {
	public static class MyTask implements Runnable {
		@Override
		public void run() {
			System.out.println(System.currentTimeMillis() + ":Thread ID:"
					+ Thread.currentThread().getId());

			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		MyTask task = new MyTask();
		ExecutorService es = new ThreadPoolExecutor(5, 5,
				0L, TimeUnit.MILLISECONDS,
				new LinkedBlockingDeque<Runnable>(10),
				Executors.defaultThreadFactory(),
				new RejectedExecutionHandler() {
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor  executor) {    
						System.out.println(r.toString() + "is discard");
					}
				});

		for(int i=0; i<Integer.MAX_VALUE; i++)  {
			es.submit(task);
			Thread.sleep(10);
		}
	}
}

public class RejectThreadPoolDemo {
	public static class MyTask implements Runnable {
		@Override
		public void run() {
			System.out.println(System.currentTimeMillis() + ":Thread ID:"
					+ Thread.currentThread().getId());

			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		MyTask task = new MyTask();
		ExecutorService es = new ThreadPoolExecutor(5, 5,
				0L, TimeUnit.MILLISECONDS,
				new LinkedBlockingDeque<Runnable>(10),
				Executors.defaultThreadFactory(),
				new RejectedExecutionHandler() {
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor  executor) {    
						System.out.println(r.toString() + "is discard");
					}
				});

		for(int i=0; i<Integer.MAX_VALUE; i++)  {
			es.submit(task);
			Thread.sleep(10);
		}
	}
}

1.4 ThreadFactory自定义线程创建

ThreadFactory是一个接口,它只有一个方法,用来创建线程:

Thread newThread(Runnable r);

Thread newThread(Runnable r);

下面是使用自定义ThreadFactory的案例,一方面记录线程的创建,另一方面将所有线程设置为守护线程,这样主线程退出,将会强制销毁线程池。

public static void main(String[] args) throws InterruptedException {
	MyTask task = new MyTask();
	ExecutorService es = new ThreadPoolExecutor(5,5,
			0L, TimeUnit.MILLISECONDS,
			new SynchronousQueue<Runnable>(),
			new ThreadFactory() {
				@Override
				public Thread newThread(Runnable r) {
					Thread t = new Thread(r);
					t.setDaemon(true);
					System.out.println("create " + t);
					return t;
				}
			});
	for(int i=0; i<5; i++) {
		es.submit(task);
	}
	Thread.sleep(2000);
}


public static void main(String[] args) throws InterruptedException {
	MyTask task = new MyTask();
	ExecutorService es = new ThreadPoolExecutor(5,5,
			0L, TimeUnit.MILLISECONDS,
			new SynchronousQueue<Runnable>(),
			new ThreadFactory() {
				@Override
				public Thread newThread(Runnable r) {
					Thread t = new Thread(r);
					t.setDaemon(true);
					System.out.println("create " + t);
					return t;
				}
			});
	for(int i=0; i<5; i++) {
		es.submit(task);
	}
	Thread.sleep(2000);
}

1.5 扩展线程池

虽然JDK实现了非常稳定的高性能线程池,但是我们可以对这个线程池做一些扩展,增强线程池的功能。ThreadPoolExecutor是一个可以扩展的线程池。它提供了beforeExecute()、afterExecute()、terminated()三个接口对线程池进行控制。这三个方法分别用于记录一个任务的开始、结束和整个线程池的退出。下面演示了对线程池的扩展,在这个拓展中,我们将记录每一个任务的执行日志。

public class ExtThreadPool {
	public static class MyTask implements Runnable {
		public String name;
		public MyTask(String name) {
			this.name = name;
		}
		@Override
		public void run() {
			System.out.println("正在执行" + ":Thread ID:" + Thread.currentThread().getId()
					+ ",Task Name=" + name);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ExecutorService es = new ThreadPoolExecutor(5,5,0L,TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>()) {
			@Override
			protected void beforeExecute(Thread t, Runnable r) {
				System.out.println("准备执行:" + ((MyTask)r).name);
			}
			@Override
			protected void afterExecute(Runnable r, Throwable t) {
				System.out.println("执行完成:" + ((MyTask)r).name);
			}
			@Override
			protected void terminated() {
				System.out.println("线程池退出");
			}
		};
		for(int i=0; i<5; i++) {
			MyTask task = new MyTask("TASK-GEYM-" + i);
			es.execute(task);
			Thread.sleep(10);
		}
		es.shutdown();
	}
}

public class ExtThreadPool {
	public static class MyTask implements Runnable {
		public String name;
		public MyTask(String name) {
			this.name = name;
		}
		@Override
		public void run() {
			System.out.println("正在执行" + ":Thread ID:" + Thread.currentThread().getId()
					+ ",Task Name=" + name);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ExecutorService es = new ThreadPoolExecutor(5,5,0L,TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>()) {
			@Override
			protected void beforeExecute(Thread t, Runnable r) {
				System.out.println("准备执行:" + ((MyTask)r).name);
			}
			@Override
			protected void afterExecute(Runnable r, Throwable t) {
				System.out.println("执行完成:" + ((MyTask)r).name);
			}
			@Override
			protected void terminated() {
				System.out.println("线程池退出");
			}
		};
		for(int i=0; i<5; i++) {
			MyTask task = new MyTask("TASK-GEYM-" + i);
			es.execute(task);
			Thread.sleep(10);
		}
		es.shutdown();
	}
}

1.6 优化线程池线程数量

线程池的大小对系统性能有一定的影响,过大或过小的线程数量都无法发挥最优的系统性能,因此要避免极大和极小两种情况。 在《java Concurrency in Practice》中给出了一个估算线程池大小的经验公式:

Ncpu = CPU数量
Ucpu = 目标CPU的使用率(0 ≤ Ucpu ≤ 1 )
W/C = 等待时间与计算时间的比率
最优的池大小等于
Nthreads = Ncpu * Ucpu * (1+W/C)

在java中可以通过Runtime.getRuntime().availableProcessors()取得可用CPU数量。

1.7 在线程池中寻找堆栈

向线程池讨回异常堆栈的方法。一种最简单的方法,就是放弃submit(),改用execute()。将任务提交的代码改成:

pools.execute(new DivTask(100,i));

pools.execute(new DivTask(100,i));

或者使用下面的方法改造submit():

Future re = pools.submit(new DivTask(100,i));
re.get();

Future re = pools.submit(new DivTask(100,i));
re.get();

上述方法,从异常堆栈中只能知道异常在哪里抛出的。还希望知道这个任务在哪里提交的,需要我们扩展ThreadPoolExecutor线程池,让它在调度之前,先保存一下提交任务线程的堆栈信息。如下所示:

public class TraceThreadPoolExecutor extends ThreadPoolExecutor {
	public TraceThreadPoolExecutor(int corePoolSize,int maximumPoolSize,
			long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {
		super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue);
	}
	@Override
	public void execute(Runnable task) {
		// TODO Auto-generated method stub
		super.execute(wrap(task, clientTrace(), Thread.currentThread().getName()));
	}

	@Override
	public Future<?> submit(Runnable task) {
		// TODO Auto-generated method stub
		return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
	}
	private Exception clientTrace() {
		return new Exception("Client stack trace");
	}
	private Runnable wrap(final Runnable task,final Exception clientStack, String clientThreadName) {
		return new Runnable() {
			@Override
			public void run() {
				try {
					task.run();
				} catch (Exception e) {
					clientStack.printStackTrace();
					try {
						throw e;
					} catch (Exception e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		};
	}
	public static class DivTask implements Runnable {
		int a,b;
		public DivTask(int a,int b) {
			this.a = a;
			this.b = b;
		}
		@Override
		public void run() {
			double re = a/b;
			System.out.println(re);
		}
	}

	public static void main(String[] args) {
		ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE,
				0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
		for(int i=0; i<5; i++)
			pools.execute(new DivTask(100, i));
	}
}

public class TraceThreadPoolExecutor extends ThreadPoolExecutor {
	public TraceThreadPoolExecutor(int corePoolSize,int maximumPoolSize,
			long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {
		super(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue);
	}
	@Override
	public void execute(Runnable task) {
		// TODO Auto-generated method stub
		super.execute(wrap(task, clientTrace(), Thread.currentThread().getName()));
	}

	@Override
	public Future<?> submit(Runnable task) {
		// TODO Auto-generated method stub
		return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
	}
	private Exception clientTrace() {
		return new Exception("Client stack trace");
	}
	private Runnable wrap(final Runnable task,final Exception clientStack, String clientThreadName) {
		return new Runnable() {
			@Override
			public void run() {
				try {
					task.run();
				} catch (Exception e) {
					clientStack.printStackTrace();
					try {
						throw e;
					} catch (Exception e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		};
	}
	public static class DivTask implements Runnable {
		int a,b;
		public DivTask(int a,int b) {
			this.a = a;
			this.b = b;
		}
		@Override
		public void run() {
			double re = a/b;
			System.out.println(re);
		}
	}

	public static void main(String[] args) {
		ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE,
				0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
		for(int i=0; i<5; i++)
			pools.execute(new DivTask(100, i));
	}
}

1.8 Fork/Join框架

分而治之一直是一个非常有效处理大量数据的方法。fork/join框架是ExecutorService接口的一个实现,可以帮助开发人员充分利用多核处理器的优势,编写出并行执行的程序,提高应用程序的性能;设计的目的是为了处理那些可以被递归拆分的任务。
在实际使用中,如果毫无顾忌地使用fork()开启线程进行处理,那么很有可能导致系统开启过多的线程而严重影响性能。所以,在JDK中,给出了一个ForkJoinPool线程池,对于fork()方法并不急于开启线程,而是提交给ForkJoinPool()线程池进行处理,以节省系统资源。可以向ForkJoinPool线程池提交一个ForkJoinTask任务。所谓ForkJoinTask任务就是支持fork()分解以及join()等待的任务。ForkJoinTask有两个重要的子类,RecursiveAction和RecursiveTask。它们分别代表没有返回值的任务和可以携带返回值的任务。
下面简单地展示Fork/Join框架的使用,这里用来计算数列求和:

public class CountTask extends RecursiveTask<Long> {
	private static final int THRESHOLD = 10000;
	private long start;
	private long end;
	public CountTask(long start,long end) {
		this.start = start;
		this.end = end;
	}
	public Long compute() {
		long sum = 0;
		boolean canCompute = (end - start) < THRESHOLD;
		if(canCompute) {
			for(long i=start; i<=end; i++) {
				sum += i;
			}
		} else {
			long step = (start + end)/100;
			ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
			long pos = start;
			for(int i=0; i<100; i++) {
				long lastOne = pos + step;
				if(lastOne > end) lastOne = end;
				CountTask subTask = new CountTask(pos, lastOne);
				pos += step+1;
				subTasks.add(subTask);
				subTask.fork();
			}
			for(CountTask t:subTasks) {
				sum += t.join();
			}
		}
		return sum;
	}
	public static void main(String[] args) {
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		CountTask task = new CountTask(0,200000L);
		ForkJoinTask<Long> result = forkJoinPool.submit(task);
		try {
			long res = result.get();
			System.out.println("sum="+res);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
}

public class CountTask extends RecursiveTask<Long> {
	private static final int THRESHOLD = 10000;
	private long start;
	private long end;
	public CountTask(long start,long end) {
		this.start = start;
		this.end = end;
	}
	public Long compute() {
		long sum = 0;
		boolean canCompute = (end - start) < THRESHOLD;
		if(canCompute) {
			for(long i=start; i<=end; i++) {
				sum += i;
			}
		} else {
			long step = (start + end)/100;
			ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
			long pos = start;
			for(int i=0; i<100; i++) {
				long lastOne = pos + step;
				if(lastOne > end) lastOne = end;
				CountTask subTask = new CountTask(pos, lastOne);
				pos += step+1;
				subTasks.add(subTask);
				subTask.fork();
			}
			for(CountTask t:subTasks) {
				sum += t.join();
			}
		}
		return sum;
	}
	public static void main(String[] args) {
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		CountTask task = new CountTask(0,200000L);
		ForkJoinTask<Long> result = forkJoinPool.submit(task);
		try {
			long res = result.get();
			System.out.println("sum="+res);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
}

在使用ForkJoin时需要注意,如果任务的划分层次很深,一直得不到返回,那么可能出现两种情况:第一,系统内的线程数量越积越多,导致性能严重下降。第二,函数的调用层次变得很深,最终导致栈溢出。
此外,ForkJoin线程池使用一个无锁是栈来管理空闲线程。如果一个工作线程暂时取不到可用的任务,则可能会被挂起,挂起的线程将会被压入由线程池维护的栈中。待将来有任务可用时,再从栈中唤醒这些线程。