Java线程池

线程池

线程池原理

线程池是池化技术的一种典型实现,所谓池化技术就是提前保存大量的资源,以备不时之需。在机器资源有限的情况下,使用池化技术可以大大的提高资源的利用率,提升性能等。

线程池的好处

  1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的核心参数

源码

  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

参数说明

参数名 含义 解释
corePoolSize 线程池核心线程数 默认情况下,线程池中是没有线程的,当还没有一次任务到达过时,初始化的线程数为0,当有任务初次来临,直接创建corePoolSize个线程;核心线程生命周期无限,即使空闲也不会死亡。
maximumPoolSize 线程池能创建的最大线程数 当核心线程数已满,并且工作队列也已经存放满,才会去判断当前线程数是否小于maximumPoolSize,小于则继续创建线程处理任务,等于则执行拒绝策略。
keepAliveTime 闲置超时时间 当线程池中的线程数大于corePoolSize时,此值才生效,即大于corePoolSize的线程在经过keepAliveTime的时间依然没有任务执行,则销毁线程。
unit 超时时间单位 参数keepAliveTime的单位。
workQueue 工作队列 当核心线程数已满时,新提交的任务放到任务队列中(前提是任务队列没满)。
threadFactory 线程池创建新线程的工厂 创建线程,一般默认即可。
handler 线程池达到饱和之后的拒绝策略 当线程数达到最大线程maximumPoolSize后(此时队列已经存满),再有新任务提交,执行的处理策略。

工作队列

  • ArrayBlockingQueue(数组的有界阻塞队列)
  • LinkedBlockingQueue(链表的无界阻塞队列)
  • SynchronousQueue(一个不缓存任务的阻塞队列)
  • PriorityBlockingQueue(具有优先级的无界阻塞队列)
  • DelayQueue(这是一个无界阻塞延迟队列)

拒绝策略

AbortPolicy:丢弃任务并抛出 RejectedExecutionException 异常。(默认这种)
DiscardPolicy:丢弃任务,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 。也就是当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务从队尾添加进去,等待执行。
CallerRunsPolicy:谁调用,谁处理。由调用线程(即提交任务给线程池的线程)处理该任务,如果线程池已经被shutdown则直接丢弃

简要记忆

  1. AbortPolicy:拒绝抛出异常

  2. DiscardPolicy:丢弃、不抛出异常

  3. DiscardOlestPolicy:尝试竞争第一个

  4. CallerRunsPolicy:回退到调用者

执行流程

当提交一个新任务到线程池时,具体的执行流程如下:

  1. 当我们提交任务,线程池会根据corePoolSize大小创建若干任务数量线程执行任务

  2. 当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队

  3. 当阻塞队列也满了之后,那么将会继续创建(maximumPoolSize-corePoolSize)个数量的线程来执行任务,如果任务处理完成,maximumPoolSize-corePoolSize额外创建的线程等待keepAliveTime之后被自动销毁

  4. 如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理

Java线程池_第1张图片

线程池创建方法

Excutor

  1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
  4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
  5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
  6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
  7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。

代码示例:

package com.junfeng.test.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyExecutors {

    public static void main(String[] args) {
        // 创建定长线程池
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            //创建任务
            Runnable runnable = new Runnable(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        System.out.println("当前执行的线程为:"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            //任务添加到线程池
            newFixedThreadPool.execute(runnable);
        }
    }

}

ThreadPoolExcutor

  • ThreadPoolExecutor是实际执行线程池操作的类,通过手动创建ThreadPoolExecutor对象来创建线程池。
  • 可以设置核心线程数、最大线程数、线程空闲时间等参数来配置线程池的行为。
package com.junfeng.test.concurrent;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolExecutor {

    public static void main(String[] args) {
        //最大线程数设置为2,队列最大能存2,使用主线程执行的拒绝策略
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.CallerRunsPolicy());

        //此时有6个任务,最大线程+队列能处理4个,主线程需要处理6-4=2个
        for (int i = 0; i < 10; i++) {
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        System.out.println("执行当前任务的线程:" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            threadPoolExecutor.execute(run);
        }
    }

}

submit和execute区别

  • submit是ExecutorService接口的方法;而execute是Executor的方法
  • submit有返回值Future;execute没有返回值

submit方法:

 Future submit(Callable task);

execute方法:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

线程池设置大小

cpu密集型

任务耗时低, 计算速度快, cpu等待空闲时间小。 所以设置的线程数不能太大, 以免出现抢占资源的情况和CPU时间片的切换带来的性能损耗, 一般为 N+1 。 (N 为CPU核数)。 我们Java程序中的线程池, 一般情况下是cpu密集型。

io密集型

最要消耗在io, CPU等待空闲时间大。所以设置的线程数可以考虑大一些, 一般设置为 2 * N 。 (N 为CPU核数)

通常我们只能通过上面公式,根据业务情况,大致估一个数值,在通过压测,不断的调整一个合适的线程数大小。

你可能感兴趣的:(java基础,java,开发语言)