线程池使用建议

之前写了很多资料,不过都是自己整理没有公开,如果大家感兴趣可以看下我在码云整理的资料

不过上面的资料都是随手记得,没有很好地归类梳理,有空会慢慢整理放到博客中。只有不断梳理和整理才能不断地加深记忆和提高

  • 线程池命名

可以使用ThreadFactoryBuilder google guava提供的

import com.google.common.util.concurrent.ThreadFactoryBuilder;

final ThreadFactory threadFactory = new ThreadFactoryBuilder()
        .setNameFormat("Orders-%d")
        .setDaemon(true)
        .build();
final ExecutorService executorService = Executors.newFixedThreadPool(10, threadFactory);

默认线程命名 pool-N-thread-M

N 是线程池的序号(没创建一个线程池N+1)

M 是池里线程的序号

比如 pool-2-thread-3 :指的是JVM生命周期中第二个线程池里的第三个线程

参考资料:https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadFactory.html

  • 随时修改线程名称 setName

  • 险式的安全地关闭线程

两种方式:

  1. 让所有的线程执行完成 shutdown()
  2. 舍弃线程执行直接结束 shutdownNow()

比如我们提交了N个任务到线程池,并希望全部执行完毕后才返回的话,使用shutdown()

private void sendAllEmails(List<String> emails) throws InterruptedException {
    emails.forEach(email ->
            executorService.submit(() ->
                    sendEmail(email)));
    executorService.shutdown();
    final boolean done = executorService.awaitTermination(1, TimeUnit.MINUTES);
    log.debug("All e-mails were sent so far? {}", done);
}
  • 谨慎处理中断

Future的一个较少提及的特性便是cancelling

  • 监控队列长度,确定队列有界

不当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露

如果配置的线程过少,则队列会持续变大,消耗过多内存。而过多的线程又会由于频繁的上下文切换导致整个系统的速度变缓——殊途而同归。队列的长度至关重要,它必须得是有界的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请求

final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
executorService = new ThreadPoolExecutor(n, n,
        0L, TimeUnit.MILLISECONDS,
        queue);

上面的代码等价于Executors.newFixedThreadPool(n),然而不同的是默认的实现是一个无界的LinkedBlockingQueue。这里我们用的是一个固定100大小的ArrayBlockingQueue。也就是说如果已经有100个任务在队列中了(还有N个在执行中),新的任务就会被拒绝掉,并抛出RejectedExecutionException异常。 由于这里的队列是在外部声明的,我们还可以时不时地调用下它的size()方法来将队列大小记录在到日志/JMX/或者你所使用的监控系统中。

  • 异常处理

  • 监控队列中的等待时间

  • 同步队列

SynchronousQueue是一个非常有意思的BlockingQueue。它本身甚至都算不上是一个数据结构。最好的解释就是它是一个容量为0的队列。这里引用下Java文档中的一段话:

每一个insert操作都需要等待另一个线程的一个对应的remove操作,反之亦然。同步队列内部不会有任何空间,甚至连一个位置也没有。你无法对同步队列执行peek操作,因为仅当你要移除一个元素的时候才存在这么个元素;如果没有别的线程在尝试移除一个元素你也无法往里面插入元素;你也无法对它进行遍历,因为它什么都没有。。。

同步队列与CSP和Ada中所用到的集结管道(rendezvous channel)有异曲同工之妙。

它和线程池有什么关系?你可以试试在ThreadPoolExecutor中用下SynchronousQueue:

BlockingQueue<Runnable> queue = new SynchronousQueue<>();
ExecutorService executorService = new ThreadPoolExecutor(n, n,
        0L, TimeUnit.MILLISECONDS,
        queue);

我们创建了一个拥有两个线程的线程池,以及一个SynchronousQueue。由于SynchronousQueue本质上是一个容量为0的队列,因此这个ExecutorService只有当有空闲线程的时候才能接受新的任务。如果所有的线程都在忙,新的任务便会马上被拒绝掉,不会进行等待。这在要么立即执行,要么马上丢弃的后台执行的场景中会非常有用。

要么立即执行,要么马上丢弃

猜你喜欢

转载自my.oschina.net/kipeng/blog/1795548