Java线程池

132 阅读5分钟

1:为什么要使用线程池

线程对于进程来说,虽然已经十一种轻量级的工具,但是线程的创建和销毁还是会消耗资源,这个资源包括时间和空间。

对于线程的使用还是要掌握一个度,在有限范围内增加线程的数量可以明显的提高系统的吞吐量,但是以但超过了某个阈值,就可能会拖垮系统。


2:什么是线程池

为了避免系统频繁的创建和销毁线程,我们可以让创建的线程复用

线程池和数据库连接池都是类似的概念

image.png

3:jdk对线程池的支持

强大的jdk当然有线程池的实现

jdk提供了一套Executor框架,帮助开发人员进行线程控制,其本质就是一个线程池,核心成员如下: image.png 其中Executors扮演者线程池工厂的角色,通过此工厂可以获得一个特定的线程池。

Executor提供了各种线程池的实现,比较常用的是一下工厂方法得到的线程池:

- public static ExecutorService newFixedThreadPool(int nThreads)

该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变。该线程池有一个任务队列,如果线程池中有空闲线程,就会到该任务队列中取一个任务执行

-public static ExecutorService newSingleThreadExecutor()

该方法返回一个只有一个线程的线程池。若大于一个任务背提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务

-public static ExecutorService newCachedThreadPool()

该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但是如果有空闲线程可以复用,则会优先复用这些空闲的线程。若没有空闲的线程,但是又有新的线程提交,就会创建新的线程处理。

- public static ScheduledExecutorService newSingleThreadScheduledExecutor()

该方法返回一个ScheduledExecutorService对象,线程池大小为一。ScheduledExecutorService在ExecutorService接口上扩展了在给定时间执行某任务的功能,如在某个固定的时延之后执行,或者周期性执行某个任务

-public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

该方法返回一个ScheduledExecutorService对象,但可以指定该线程池的线程数量。

3.1:非计划任务线程池

以newFixedThreadPool(int nThreads)为例: image.png

image.png

image.png

3.2:计划任务线程池

该方法返回一个ScheduledExecutorService对象,可以根据时间需要对线程进行调度。

newSingleThreadScheduledExecutor() 方法返回的也是这个对象

该对象有三个主要的方法: -public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit);

schedule()会在给定时间,对任务进行一次调度。

- public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);

image.png - public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit); image.png

4:线程池的核心实现

上面的工厂方法基本调用了下面的构造函数返回一个线程池对象

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

参数含义如下:

  • corePoolSize:指定了线程池中的线程数量
  • maximumPoolSize:指定了线程池中最大线程数量
  • keepAliveTime:当线程的数量超过corePoolSize时,多余的线程的存活时间,即多余的线程多久之后应该被销毁
  • unit:时间单位
  • workQueue:任务队列,被提交但是没有被执行的任务
  • threadFactory:线程工厂,用于创建线程,一般默认即可
  • handler:拒绝策略。任务太多来不及执行时,如何拒绝任务

这些参数中,除了任务队列和拒绝策略,其他的都容易理解

4.1:任务队列

根据队列功能分类,钩爪函数中可以使用以下几种队列

  • 直接提交的队列

该功能由SynchronousQueue对象提供。SynchronousQueue没有容量,每次插入操作都要等待一个响应的删除操作,反之每一个删除操作都要等待一个插入操作。使用SynchronousQueue提交的任务并不会被真实的保存,而每次总是将新任务交给线程执行,如果没有空闲的线程,就尝试创建新的线程,如果线程数量达到最大值,就执行拒绝策略。所以使用这种队列时,如果最大线程数不是很大很容易执行拒绝策略

  • 有界的任务队列

有界的任务队列可以使用ArrayBlockingQueue实现。ArrayBlockingQueue的构造函数必须带有一个容量参数,表示该队列得到最大容量。当使用该队列时,如果实际线程数小于corePoolSize,就会优先创建线程,若大于corePoolSize,则会将任务加入等待队列。如果等待队列已满,无法加入,则在线程数量不超过maximumPoolSize的前提下创建新线程,否则执行拒绝策略。可见,只有任务队列已满,线程数量才会超过corePoolSize

  • 无界的任务队列

无界的任务队列可以通过LinkedBlockingQueue实现。与有界队列相比,除非系统资源耗尽,否则不会存在任务入队列失败的情况。当新任务来到,线程数小于corePoolSize时,线程池会增加新线程执行任务,但当系统达到corePoolSize后,就不会继续增加了。后续的任务只能使用空闲线程或者加入任务队列。

  • 优先的任务队列

带有执行优先级的队列。它通过PriorityBlockingQueue实现,可以控制任务执行的先后顺序。

image.png

4.2:拒绝策略

最后一个参数handler指定了拒绝策略。JDK有四种内置的拒绝策略。

image.png

5:Future

futurnTask