首页 > 科技 >

Java中线程池,你真的会用吗?

2019-07-20 13:02:09 暂无 阅读:1902 评论:0

在《深入源码剖析Java线程池的实现道理》这篇文章中,我们介绍过了Java中线程池的常见用法以及根基道理。

在文中有如许一段描述:能够经由Executors静态工场构建线程池,但一样不建议如许使用。

关于这个问题,在那篇文章中并没有深入的睁开。作者之所以这么说,是因为这种建立线程池的体式有很大的隐患,稍有失慎就有或者导致线上故障。

本文我们就来环绕这个问题来剖析一下为什么JDK自身供应的构建线程池的体式并不建议使用?究竟应该若何建立一个线程池呢?

Executors

Executors 是一个Java中的对象类。供应工场方式来建立分歧类型的线程池。

Java中线程池,你真的会用吗?

从上图中也能够看出,Executors的建立线程池的方式,建立出来的线程池都实现了ExecutorService接口。常用方式有以下几个:

newFiexedThreadPool(int Threads):建立固定命目线程的线程池。

newCachedThreadPool():建立一个可缓存的线程池,挪用execute 将重用以前组织的线程(若是线程可用)。若是没有可用的线程,则建立一个新线程并添加到池中。完结并从缓存中移除那些已有 60 秒钟未被使用的线程。

newSingleThreadExecutor()建立一个单线程化的Executor。

newScheduledThreadPool(int corePoolSize)建立一个支撑准时及周期性的义务执行的线程池,多数情形下可用来替代Timer类。

类看起来功能照样对照壮大的,又用到了工场模式、又有对照强的扩展性,主要的是用起来还对照轻易,如:ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;

即可建立一个固定巨细的线程池。

然则为什么我说不建议人人使用这个类来建立线程池呢?

我提到的是『不建议』,然则在阿里巴巴Java斥地手册中也明确指出,并且用的词是『不许可』使用Executors建立线程池。

Java中线程池,你真的会用吗?

Executors存在什么问题

在阿里巴巴Java斥地手册中提到,使用Executors建立线程池或者会导致OOM(OutOfMemory ,内存溢出),然则并没有解说为什么,那么接下来我们就来看一下究竟为什么不许可使用Executors?

我们先来一个简洁的例子,模拟一下使用Executors导致OOM的情形。/**

* @author Hollis

*/

public class ExecutorsDemo {

private static ExecutorService executor = Executors.newFixedThreadPool(15);

public static void main(String[] args) {

for (int i = 0; i < Integer.MAX_VALUE; i++) {

executor.execute(new SubThread());

}

}

}

class SubThread implements Runnable {

@Override

public void run() {

try {

Thread.sleep(10000);

} catch (InterruptedException e) {

//do nothing

}

}

}

经由指定JVM参数:-Xmx8m -Xms8m 运行以上代码,会抛出OOM:Exception in thread \"main\" java.lang.OutOfMemoryError: GC overhead limit exceeded

at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)

at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

以上代码指出,ExecutorsDemo.java的第16行,就是代码中的executor.execute(new SubThread());。

Executors为什么存在缺陷

经由上面的例子,我们知道了Executors建立的线程池存在OOM的风险,那么究竟是什么原因导致的呢?我们需要深入Executors的源码来剖析一下。

其实,在上面的报错信息中,我们是能够看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致OOM的其实是LinkedBlockingQueue.offer方式。Exception in thread \"main\" java.lang.OutOfMemoryError: GC overhead limit exceeded

at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)

at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)

若是读者翻看代码的话,也能够发现,其实底层的确是经由LinkedBlockingQueue实现的:public static ExecutorService newFixedThreadPool(int nThreads) {

return new ThreadPoolExecutor(nThreads, nThreads,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue<Runnable>());

若是读者对Java中的壅塞队列有所认识的话,看到这里或许就可以领略原因了。

Java中的BlockingQueue首要有两种实现,离别是ArrayBlockingQueue 和 LinkedBlockingQueue。

ArrayBlockingQueue是一个用数组实现的有界壅塞队列,必需设置容量。

LinkedBlockingQueue是一个用链表实现的有界壅塞队列,容量能够选择进行设置,不设置的话,将是一个无界限的壅塞队列,最大长度为Integer.MAX_VALUE。

这里的问题就出在:不设置的话,将是一个无界限的壅塞队列,最大长度为Integer.MAX_VALUE。也就是说,若是我们不设置LinkedBlockingQueue的容量的话,其默认容量将会是Integer.MAX_VALUE。

而newFixedThreadPool中建立LinkedBlockingQueue时,并未指定容量。此时,LinkedBlockingQueue就是一个无界限队列,对于一个无界限队列来说,是能够络续的向队列中到场义务的,这种情形下就有或者因为义务过多而导致内存溢出问题。

上面提到的问题首要施展在newFixedThreadPool和newSingleThreadExecutor两个工场方式上,并不是说newCachedThreadPool和newScheduledThreadPool这两个方式就平安了,这两种体式建立的最大线程数或者是Integer.MAX_VALUE,而建立这么多线程,必然就有或者导致OOM。

建立线程池的准确姿势

避免使用Executors建立线程池,首要是避免使用个中的默认实现,那么我们能够本身直接挪用ThreadPoolExecutor的组织函数来本身建立线程池。在建立的同时,给BlockQueue指定容量就能够了。private static ExecutorService executor = new ThreadPoolExecutor(10, 10,

60L, TimeUnit.SECONDS,

new ArrayBlockingQueue(10));

这种情形下,一旦提交的线程数跨越当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前方程池使用的队列是有界限队列,队列已经满了便无法持续处理新的恳求。然则非常(Exception)总比发生错误(Error)要好。

除了本身界说ThreadPoolExecutor外。还有其他方式。这个时候第一时间就应该想到开源类库,如apache和guava等。

作者介绍使用guava供应的ThreadFactoryBuilder来建立线程池。public class ExecutorsDemo {

private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()

.setNameFormat(\"demo-pool-%d\").build();

private static ExecutorService pool = new ThreadPoolExecutor(5, 200,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

public static void main(String[] args) {

for (int i = 0; i < Integer.MAX_VALUE; i++) {

pool.execute(new SubThread());

}

}

}

经由上述体式建立线程时,不光能够避免OOM的问题,还能够自界说线程名称,加倍轻易的失足的时候溯源。

思虑题,文中作者说:发生非常(Exception)要比发生错误(Error)好,为什么这么说?

原文地址:https://dwz.cn/Rqh5qKhe

作者: Hollis

相关文章