多线程_线程池

348 阅读7分钟

一、线程池介绍

概念

线程池,本质上是一种对象池,用于管理线程资源。

在任务执行之前,需要从线程池中拿出线程来执行。

在任务执行完成之后,需要把线程放回线程池。

通过线程的这种反复机制,可以有效地避免直接创建线程所带来的坏处。

池化技术

为了减少对对象创建和销毁时造成的消耗,提前在某个容器里创建大量的对象资源,需要使用时从容器内获取对象,使用完毕时再将对象还回容器中以便重复使用。

该技术就是池化技术,能提高程序的性能,在高并发条件下效果更加明显,比较典型的池化技术有线程池、连接池、内存池和对象池等。

单线程的缺点

  1. 频繁的线程创建和销毁会占用更多的CPU和内存。
  2. 频繁的线程创建和销毁会对GC产生比较大的压力。
  3. 线程太多,线程切换带来的开销将不可忽视。
  4. 线程太少,多核CPU得不到充分利用,是一种浪费。

线程池的优点

  1. 降低资源的消耗。线程本身是一种资源,创建和销毁线程会有CPU开销;创建的线程也会占用一定的内存。
  2. 提高任务执行的响应速度。任务执行时,可以不必等到线程创建完之后再执行。
  3. 提高线程的可管理性。线程不能无限制地创建,需要进行统一的分配、调优和监控。

线程池流程

  1. 创建完线程池后,开始等待任务请求。

  2. 当线程池执行execute()方法时,将任务进行如下判断:

    • 判断核心线程数是否已满,未满执行该任务。
    • 判断阻塞队列是否已满,未满将任务放入阻塞队列中。
    • 判断线程池是否已满,未满由临时线程执行任务。
    • 线程池已满,执行拒绝策略。
  3. 当线程完成任务时,会从阻塞队列中取下一个任务来执行。

  4. 当线程无事可做时,线程池会进行如下判断:

    • 空闲时间是否超过空闲时间,未超过继续等待。
    • 当前线程总数是否大于核心线程数,未超过继续等待。
    • 已超过则停掉临时线程,直至到核心线程数为止。

二、线程池创建

1.newFixedThreadPool

一个任务一个任务执行的场景。

创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。

源码

	/**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.  
     * ----------------------------------------------------------------
     * 创建一个执行器,该执行器使用单个工作线程操作一个未绑定队列。
     * (但是请注意,如果这个线程在关闭之前由于执行失败而终止,
     * 那么在需要执行后续任务时,一个新的线程将取代它。)
     * 任务保证按顺序执行,并且在任何给定时间内活动的任务不超过一个。
     * 与等效的{@code newFixedThreadPool(1)}不同,
     * 返回的执行器保证不会被重新配置以使用额外的线程。
     * ---------------------------------------------------------------
     * @return the newly created single-threaded Executor
     */
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

2.newFixedThreadPool

执行长期的任务,性能好很多。

创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。

源码

/**
 * Creates a thread pool that reuses a fixed number of threads
 * operating off a shared unbounded queue.  At any point, at most
 * {@code nThreads} threads will be active processing tasks.
 * If additional tasks are submitted when all threads are active,
 * they will wait in the queue until a thread is available.
 * If any thread terminates due to a failure during execution
 * prior to shutdown, a new one will take its place if needed to
 * execute subsequent tasks.  The threads in the pool will exist
 * until it is explicitly {@link ExecutorService#shutdown shutdown}.
 * ------------------------------------------------------------------
 * 创建一个线程池,该线程池重用固定数量的线程在一个共享的无边界队列上操作。
 * 在任何时候,最多{@code nThreads}的线程将是活动的处理任务。
 * 如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,
 * 直到有一个线程可用为止。如果任何线程在关闭前的执行过程中由于失败而终止,
 * 那么在需要执行后续任务时,将有一个新的线程替代它。
 * 池中的线程将一直存在,直到显式地为{@link ExecutorService#shutdown shutdown}。
 * ---------------------------------------------------------------------
 * @param nThreads the number of threads in the pool
 * @return the newly created thread pool
 * @throws IllegalArgumentException if {@code nThreads <= 0}
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

3.newScheduledThreadPool

周期性执行任务的场景。

创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构。

源码

/**
     * Creates a thread pool that can schedule commands to run after a
     * given delay, or to execute periodically.
     * @param corePoolSize the number of threads to keep in the pool,
     * even if they are idle
     * ----------------------------------------------------------------
     * 创建一个线程池,它可以调度命令在给定的延迟后运行或定期执行。
     * @param corePoolSize保留在池中的线程数,即使它们是空闲的。
     * ----------------------------------------------------------------
     * @return a newly created scheduled thread pool
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

/**
 * Creates a new {@code ScheduledThreadPoolExecutor} with the
 * given core pool size.
 * --------------------------------------------------------------------
 * 使用给定的核心池大小创建一个新的{@code ScheduledThreadPoolExecutor}。
 * --------------------------------------------------------------------
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

4.newCachedThreadPool

执行很多短期异步的小程序或者负载较轻的服务器。

当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。

源码

/**
 * Creates a thread pool that creates new threads as needed, but
 * will reuse previously constructed threads when they are
 * available, and uses the provided
 * ThreadFactory to create new threads when needed.
 * -----------------------------------------------------------------
 * 创建一个线程池,该线程池根据需要创建新线程,但在可用时将重用之前构建的线程,
 * 并在需要时使用提供的ThreadFactory创建新线程。
 * ----------------------------------------------------------------
 * @param threadFactory the factory to use when creating new threads
 * @return the newly created thread pool
 * @throws NullPointerException if threadFactory is null
 */
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

* 注意事项

通常情况下均不使用上述的方法创建,原因如《阿里巴巴Java开发手册》所述,因为默认使用了LinkedBlockQueue和SynchronizedQueue,都是无边界的阻塞队列,最大长度为Integer.MAX_VALUE。因为可以不断地向队列中加入任务,所以会导致OOM。

所以通常是自己定义创建线程池来使用,如下:

ExecutorService threadPool = new ThreadPoolExecutor(
                3,
                5,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

三、线程池的七个参数

1.corePoolSize

核心线程数,线程池中的常驻核心线程数。

2.maximumPoolSize

最大线程数,线程池中能容纳同时执行的最大线程数(maximumPoolSize≥1)。

3.keepAliveTime

存活时间,多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁,知道剩corePoolSize个线程为止。

4.unit

存活时间单位,keepAliveTime的单位,在java.util.concurrent.TimeUnit类里取值。

5.workQueue

阻塞队列,被提交但尚未被执行的任务。当线程池中的线程数超过corePoolSize时,任务将放在阻塞队列里,它是一个BlockingQueue类型的对象。

  • ArrayBlockingQueue,队列是有界的,基于数字实现的阻塞队列。
  • LinkedBlockingQueue,队列可以有界,也可以无界。基于链表实现的阻塞队列。
  • SynchronousQueue,不存储元素的阻塞队列,内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。
  • PriorityBlockingQueue,带优先级的无界阻塞队列。

6.threadFactoty

线程工厂,用于创建线程,一般使用默认Executors.defaultThreadFactory()。

	/**
     * The default thread factory
     * 创建一个默认线程,名称为pool-{poolNum}-thread-{threadNum}
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

7.handler

拒绝策略,当任务队列满了并且任务线程大于等于线程池的maximumPoolSize时,使用拒绝策略来进行回调处理。

四、线程池的拒绝策略

当阻塞队列和线程都满了时, 线程池会对后续进来的任务进行处理:抛异常提醒、直接丢弃、与旧任务替换或者返回调用者。

1.AbortPolicy

ThreadPoolExecutor.AbortPolicy

这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出RejectedExecutionException异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。

2.DiscardPolicy

ThreadPoolExecutor.DiscardPolicy

使用此策略,直接丢弃该任务,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,本人的博客网站统计阅读量就是采用的这种拒绝策略。

3.DiscardOldestPolicy

ThreadPoolExecutor.DiscardOldestPolicy

此拒绝策略,是一种喜新厌旧的拒绝策略,丢弃阻塞队列中最老的任务,在添加新任务。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。

4.CallerRunsPolicy

ThreadPoolExecutor.CallerRunsPolicy

如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大。

五、线程池方法

1.execute()

接口Executor的方法,用于提交不需要返回结果的任务。

void execute(Runnable command);
package org.example.thread;

import java.util.concurrent.*;

public class test {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                3, 5, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        threadPool.execute(()->{
            System.out.println("hello word!");
        });
    }
}

2.submit()

接口ExecutorService的方法,用于提交需要返回结果的任务。

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

该方法会返回Future对象,通过使用该对象的get()方法就可以获得返回结果,但是get()方法会一直阻塞直至结果的返回。

package org.example.thread;

import java.util.concurrent.*;

public class test {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        ExecutorService threadPool = new ThreadPoolExecutor(
                3,
                5,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        Future<String> submit = threadPool.submit(() -> {
            return "hello word";
        });
        System.out.println(submit.get());
        // System.out.println(submit.get(1L, TimeUnit.SECONDS));
    }
}

3.shutdown()

将线程池状态置为SHUTDOWN,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

4.shutdownNow()

会将线程池状态置为SHUTDOWN,对所有线程执行interrupt()操作,清空队列,并将队列中的任务返回回来。

public List<Runnable> shutdownNow() {
     List<Runnable> tasks;
     final ReentrantLock mainLock = this.mainLock;
     mainLock.lock();
     try {
         checkShutdownAccess();
         advanceRunState(STOP);
         interruptWorkers();
         tasks = drainQueue();
     } finally {
         mainLock.unlock();
     }
     tryTerminate();
     return tasks;
 }

关闭案例

package org.example.thread;

import java.util.concurrent.*;

public class test {
    public static void main(String[] args)  {
        ExecutorService threadPool = new ThreadPoolExecutor(
                3, 5, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        Future<String> submit = threadPool.submit(() -> {
            return "hello word";
        });
        try {
            System.out.println(submit.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } finally {
            if (threadPool != null) {
                threadPool.shutdown();
            }
        }
    }
}

六、线程池的参数配置

1.CPU密集型

CPU使用率较高时,CPU资源消耗就大。所以CPU密集型适合运算,比如计算圆周率或对高清视频进行解码等。如果任务多,并且花在任务切换时间多,CPU执行任务的效率就低下,不适合使用CPU密集型。

计算方法 = CPU核数 + 1

2.I/O密集型

I/O密集型适合读写,当CPU使用率较低时,程序中的I/O操作会占用大量的时间,比如对数据库的读写操作等,消耗CPU的资源很少,任务的大部分时间都在在等待IO操作。

计算方法1 = CPU核数 * 2

计算方法2 = CPU ( 1 - 阻塞系数 ) 【阻塞系数在0.8~0.9之间】

* 查询CUP核数方法

  • Java代码
package org.example.thread;

public class test {
    public static void main(String[] args) {
        System.out.println("cpu核心数:" + Runtime.getRuntime().availableProcessors());
    }
}
  • 任务管理器

  • 计算机管理-处理器

资料参考:www.jianshu.com/p/7ab4ae944…