转载

Java多线程之线程池使用

Java多线程之线程池使用

前言

学习使用线程池,而不是每次用线程的时候手动去创建,然后再进行销毁,浪费系统资源。

对此线程池应运而生,将一些线程进行复用,循环使用,等下一次业务请求时候,提前创建好的线程对其做一些处理。

线程池简介

为了避免系统频繁的创建和销毁线程,我们可以将创建的线程进行复用,提前创建好一定数量的线程,对外直接提供使用,而不是等到想用的时候手动去创建线程,使用完成之后,再去销毁这个线程,尽可能的复用创建的这些线程。

譬如 :数据库中的数据库连接池,tomcat连接池大小等。

线程池 就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

关于线程需要知道的事

  • 使用多线程目的是为了充分利用cpu并发多做事
  • 线程并不是越多越好
  • 线程创建,销毁都很消耗资源
  • 多线程不一定就快

使用线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

JDK中提供的线程池

为了让我们更好使用和管理线程池,在JDK1.5中 提供了 Executor 框架,处于java.util.concurrent

Executor 下的接口和类继承关系

Java多线程之线程池使用

ExecutorService 接口中的方法

Java多线程之线程池使用

常用方法说明

  • shutdownNow:执行该方法,线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。

它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

  • shutdown:当线程池调用该方法时,线程池的状态则立刻变成SHUTDOWN状态。此时,则不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常。但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
  • isShutDown:当调用shutdown()或shutdownNow()方法后返回为true。
  • isTerminated:当调用shutdown()方法后,并且所有提交的任务完成后返回为true;
  • isTerminated:当调用shutdownNow()方法后,成功停止后返回为true;
  • submit:像线程池中提交任务,且带返回值。
  • execute (父接口中的方法):像线程池中提交任务,无返回值。

创建线程池

创建线程时候一般使用java.util.concurrent 包中提供的工具类 Executors 创建线程池。

可以看到此类提供的一些方法

Java多线程之线程池使用

常用的有四个方法用来创建不同的线程池

public static ExecutorService newFixedThreadPool()
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool()
  • newFixedThreadPool:该方法返回一个固定线程数量的线程池;

适用于为了满足资源管理的需求,但需要限制当前线程数量的应用场景,使用需负载比较重的服务器。

newFixedThreadPool 使用无界队列,则线程池中线程数超过核心线程数任务会在队列中等待,线程不会超过核心线程,最大线程数设置将无效,存活时间参数也将无效,所以如果不使用shutdown或shutdownNow方法停止线程池添加任务,不会调用决绝策略拒绝任务。

  • newSingleThreadExecutor:该方法返回一个只有一个线程的线程池;

适用于保证顺序的指定各个任务;并且任意时间点,不会存在多线程是活动的应用场景。

核心线程数和最大线程数都是1,如果是使用无界队列,影响和 newFixedThreadPool 一致

  • newCachedThreadPool:返回一个可以根据实际情况调整线程数量的线程池;

是大小无界的线程池,适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。

newCachedThreadPool是一个根据需要创建线程的线程池,核心线程数为0,最大线程数是int最大值,newCachedThreadPool 是使用没有容量的 SynchronousQueue 作为队列的,但最大线程数(int最大值)是无界的。如果提交的任务速度高于线程处理的速度,那么线程池会不断创建线程,直到资源耗尽;

  • newSingleThreadScheduledExecutor:该方法和 newSingleThreadExecutor 的区别是给定了时间执行某任务的功能,可以进行定时执行等;
  • newScheduledThreadPool:在newSingleThreadScheduledExecutor的基础上可以指定线程数量。

适用于定期执行任务的场景,可以给定延迟之后的运行任务或者定期执行任务,与 Timer 相似但是功能更强大,Time是单个后台线程,

newSingleThreadScheduledExecutor 可以构造多个线程。DelayedWorkQueue是一个无界队列 所以最大线程参数没什么意义。

经过查看源码之后我们可以发现 上面四种创建线程方法调用的还是 ThreadPoolExecutor

Java多线程之线程池使用

Java多线程之线程池使用

Java多线程之线程池使用

在 Executors 内部创建线程池的时候,实际创建的都是一个 ThreadPoolExecutor 对象,只是对 ThreadPoolExecutor 构造方法,进行了默认值的设定。

ThreadPoolExecutor 的构造方法

Java多线程之线程池使用

其中这7个参数意义表示得含义是

1、corePoolSize 核心线程池大小;

当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

2、maximumPoolSize 线程池最大容量大小;

线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

3、keepAliveTime 线程池空闲时,线程存活的时间;

线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

4、TimeUnit 时间单位;

可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

Java多线程之线程池使用

5、ThreadFactory 线程工厂

用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。

默认是使用自带的线程工厂方法创建线程

Java多线程之线程池使用

一些构造方法中也可自定义创建线程工厂,一般我们使用默认就可以。

6、BlockingQueue任务队列;

用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

7、RejectedExecutionHandler 线程拒绝策略

如果放入LinkedBlockingQueue中的任务超过整型的最大数时,抛出RejectedExecutionException。

默认自带的拒绝则侧,直接抛出异常(有界队列情况下),也可以自定义拒绝策略。

Java多线程之线程池使用

创建线程池

首先根据 Executors 创建线程池 Executors.newScheduledThreadPool(10);

但Idea中阿里守约提示以下信息,不建议使用Executors 去创建线程。

Java多线程之线程池使用

原因是因为:这样子创建线程,线程池中线程最大大小默认情况下是int最大取值大小的队列。

  • newFixedThreadPool和newSingleThreadExecutor:

主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

  • newCachedThreadPool和newScheduledThreadPool:

主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

源码如下

Java多线程之线程池使用

所以我们尽量使用 ThreadPoolExecutor 方法区创建线程池。

创建了一个核心线程是5最大线程是10 等待回收时间是60s 队列长度是100的一个线程池

返回值得任务

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));

        ArrayList<String> list = new ArrayList();

        for (int i = 0; i < 5; i++) {

            Future<String> submit = poolExecutor.submit(() -> "---> " + new Random().nextInt(3000));

            try {
                list.add(submit.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

        }
        System.out.println("::" + poolExecutor.isTerminating());
        poolExecutor.shutdown();

        list.forEach(s -> System.out.println(s));

结果如下

---> 2321

---> 2957

---> 1381

---> 132

---> 1307

创建线程基本区别不大,唯一区别在于提交任务是否有返回值

  • execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。通过以下代码可知 execute() 方法输入的任务是一个 Runnable 类的实例。
  • submit() 方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
原文  https://segmentfault.com/a/1190000022509179
正文到此结束
Loading...