Java并发(11)- 有关线程池的10个问题

 

引言

在日常开发中,线程池是使用非常频繁的一种技术,无论是服务端多线程接收用户请求,还是客户端多线程处理数据,都会用到线程池技术,那么全面的了解线程池的使用、背后的实现原理以及合理的优化线程池的大小等都是非常有必要的。这篇文章会通过对一系列的问题的解答来讲解线程池的基本功能以及背后的原理,希望能对大家有所帮助。

  • 举个例子来说明为什么要使用线程池,有什么好处?
  • jdk1.8中提供了哪几种基本的线程池?
  • 线程池几大组件的关系?
  • ExecutorService的生命周期?
  • 线程池中的线程能设置超时吗?
  • 怎么取消线程池中的线程?
  • 如何设置一个合适的线程池大小?
  • 当使用有界队列时,如何设置一个合适的队列大小?
  • 当使用有界队列时,如果队列已满,如何选择合适的拒绝策略?
  • 如何统计线程池中的线程执行时间?

举个例子来说明为什么要使用线程池,有什么好处?

先来看这样一个场景,服务端在一个线程内通过监听8888端口来接收多个客户端的消息。为了避免阻塞主线程,每收到一个消息,就开启一个新的线程来处理,这样主线程就可以不停的接收新的消息。不使用线程池时代码的简单实现如下:

public static void main(String[] args) throws IOException {     ServerSocket serverSocket = new ServerSocket(8888);      while (true) {         try {             Socket socket = serverSocket.accept();              new Thread(() -> {                 try {                     InputStream inputStream = socket.getInputStream();                     //do something                 } catch (IOException e) {                     e.printStackTrace();                 }             }).start();          } catch (IOException e) {         }     } }

通过每次new一个新的线程的方式,不会阻塞主线程,提高了服务端接收消息的能力。但是存在几个非常明显的问题:

  • 不停初始化线程的内存消耗,任何时候资源都是有限的,无限制的新建线程会占用大量的内存空间。
  • 在CPU资源有限的情况下,新建更多的线程不仅不能达到并发处理客户端消息的目的,相反由于线程间的切换更加频繁,会导致处理时间更长,效率更加低下。
  • 线程本身的创建与销毁都需要耗费服务器资源。
  • 不方便对线程进行集中管理。
    而这些问题都是可以通过使用线程池得倒解决的。

jdk1.8中提供了哪几种基本的线程池以及它们的使用场景?

  • newFixedThreadPool,固定线程数的线程池。它的核心线程数(corePoolSize)和最大线程数(maximumPoolSize)是相等的。同时它使用一个无界的阻塞队列LinkedBlockingQueue来存储额外的任务,也就是说当达到nThreads的线程数在运行之后,所有的后续线程都会进入LinkedBlockingQueue中,不会再创建新的线程。

    使用场景:因为线程数固定,一般适用于可预测的并行任务执行环境。

public static ExecutorService newFixedThreadPool(int nThreads) {     return new ThreadPoolExecutor(nThreads, nThreads,                                     0L, TimeUnit.MILLISECONDS,                                     new LinkedBlockingQueue<Runnable>()); }
  • newCachedThreadPool,可缓存线程的线程池。默认核心线程数(corePoolSize)为0,最大线程数(maximumPoolSize)为Integer.MAX_VALUE,它还有一个过期时间为60秒,当线程闲置超过60秒之后会被回收。内部使用SynchronousQueue作为阻塞队列。

    使用场景:由于SynchronousQueue无容量的特性,导致了newCachedThreadPool不适合做长时间的任务。因为如果单个任务执行时间过长,每当无空闲线程时,会导致开启新线程,而线程数量可以达到Integer.MAX_VALUE,存储队列又不能缓存任务,很容易导致OOM的问题。所以他的使用场景一般在大量短时间任务的执行上。

    public static ExecutorService newCachedThreadPool() {         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                       60L, TimeUnit.SECONDS,                                       new SynchronousQueue<Runnable>());     }
  • newSingleThreadExecutor,单线程线程池。默认核心线程数(corePoolSize)和最大线程数(maximumPoolSize)都为1,使用无界阻塞队列LinkedBlockingQueue。

    使用场景:由于只能有一个线程在执行,而且其他任务都会排队,适用于单线程串行执行有序任务的环境。

    public static ExecutorService newSingleThreadExecutor() {         return new
                    
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信