说一说Java多线程之线程池

线程池

为什么使用线程池?

  • 重复利用现有的线程继续执行任务,避免创建与销毁所带来的开销
  • 由于没有创建与销毁线程的开销,所以会提高系统的响应速度
  • 通过线程池可以对线程进行合理的管理,根据系统的承受能力调整可运行线程的数


Callable & Future

Callable与Runable的区别在于,Callable能够返回结果,返回的结果会返回一个Future对象,再通过Future对象去阻塞获取。

FutureTask

继承于Callable与Future,能够被线程池执行,且返回结果直接放在自身内部。

CompletableFutrue

案例,去各个电商平台抓取商品价格
CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(()->priceOfTM());
CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(()->priceOfTB());
CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(()->priceOfJD());

// 等待所有异步任务执行结束
CompletableFuture.allOf(futureTM, futureTB, futureJD).join();

System.out.println(futureTM.get());
System.out.println(futureTB.get());
System.out.println(futureJD.get());



ThreadPoolExecutor

newCacheThreadPool

  1. 不指定线程数,线程数最大值可以达到Integer.MAX_VALUE
  2. 线程可以缓存重复利用和回收(回收默认时间为1分钟)
  3. 当线程池中没有可用线程会创建一个线程

newFixedThreadPool

  1. 创建一个固定线程数并可重用的线程池,控制并发
  2. 线程可以重复使用,在显式关闭之前
  3. 所有的线程都处于BUSY的状态时,提交的任务必须在队列中等待

newSingleThreadExecutor

单个线程的线程执行器,线程处于BUSY的状态时,提交的任务必须在队列中等待



ScheduledThreadPool

newScheduledThreadPool

FixedThreadPool的基础上加了定时,延迟功能。

newSingleThreadScheduledExecutor

SingleThreadExecutor的基础上加了定时,延迟功能。



ForkJoinPool

CommonPool

newWorkStealingPool

基于Work Stealing算法,即一个线程对应一个队列,执行的任务平均分发到每个队列中,当一个线程空闲且对应队列为空的时候,则会去其他有任务的队列中偷(Steal)任务。

创建一个带并行级别的线程池,并行级别决定同一时刻最多有几个线程同时执行,如不传入并发级别参数,将默认与当前系统的CPU个数相同。



阿里Java规范

阿里Java开发手册

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,

这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors各个方法的弊端:

1)newFixedThreadPool和newSingleThreadExecutor:

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

2)newCachedThreadPool和newScheduledThreadPool:

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

Positive example 1//org.apache.commons.lang3.concurrent.BasicThreadFactory
    ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
       
            
Positive example 2:
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
 
    //Common Thread Pool
    ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
 
    pool.execute(()-> System.out.println(Thread.currentThread().getName()));
    pool.shutdown();//gracefully shutdown
           
            
Positive example 3<bean id="userThreadPool"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="2000" />
 
    <property name="threadFactory" value= threadFactory />
        <property name="rejectedExecutionHandler">
            <ref local="rejectedExecutionHandler" />
        </property>
    </bean>
    //in code
    userThreadPool.execute(thread);

建议启动线程数

启动线程数=CPU内核数 * CPU使用率 * (1 + IO等待时间 / 任务执行时间)

计算密集型

IO等待时间 / 任务执行时间趋向于0

需要大量网络磁盘IO

IO等待时间 / 任务执行时间趋向于1

此公式仅用于估算启动线程数,具体以测试为主。



线程池源码

ThreadPoolExecutor参数详解

    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
		··· ···
    }
参数:
  • CorePoolSize 核心线程池线程数,线程池一直持有的线程,核心线程池中的线程默认是一直开启的。
  • maxinumPoolSize允许的线程池,如果CorePool的线程都处于Busy状态,则有新的任务处理时,会开启一个新的线程。
  • keepAliveTime超出核心线程数的新创建线程保持存活的时间,在这段时间内会等待新的任务,超出这段时间始终没有新的任务来执行时,就会将这个线程销毁。
  • unit存活时间的时间单位。
  • workQueue这个队列将会保存Runnable任务;在执行方法execute(Runnable task)时,corePool已满,即正在执行的线程数等于corePoolSize时,被保存到此阻塞队列中等待执行。
  • threadFactory线程工厂,用于创建新的线程。
  • handler饱和策略,当前执行的任务数量大于maxinumPoolSize时,新的任务将会执行此策略。

阻塞队列

不同的线程池用到了不同的阻塞队列。

  • ArrayBlockingQueue 底层是数组,必须指定初始长度,生产者与消费者公用一把锁。
  • LinkedBlockingQueue底层是链表,不用指定初始长度,最大长度可达Integer.MAX_VALUE,生产者与消费者各用一把锁,生产者用PutLock,消费者用TakeLock,使得效率更高。
  • DelayQueue只有当元素的指定延迟时间到了,才能从队列中获取该元素,而且DelayQueue是一个没有大小限制的队列,因此,生产者不会被阻塞,消费者会被阻塞。
  • PriorityBlockingQueue基于优先级的的阻塞队列,根据自定义的Comparator接口来实现自定义排序,会根据优先级进行排序,底层数据结构是维护了一个二叉小顶堆实现。
  • SynchronousQueue同步队列,一种无缓冲的等待队列,消费者直接到生产者那边获取数据。
ArrayBlockingQueueLinkedBlockingQueue的区别?
  • 底层数据结构不同,前者使用数组,后者使用链表;前者需要有初始大小,后者没有,且最大长度可达Integer.MAX_VALUE
  • 效率不同,前者的生产者和消费者都共享同一把锁,后者生产者和消费者各用一把锁。

拒绝策略

  • AbortPolicy丢弃任务并抛出RejectedExecutionException异常。
  • DiscardPolicy丢弃任务不抛出异常。
  • DiscardOldestPolicy丢弃队列最前面的任务然后重新尝试执行任务。
  • CallerRunsPolicy由调用线程处理该任务(谁提交任务谁执行)。



线程池执行流程

在这里插入图片描述

假设当前任务非常多需要通过线程池执行,首先通过线程池的CorePool中的线程(线程池创建时就被创建的线程)进行执行,图中的蓝色圆圈;若CorePool线程都处于忙碌状态,则进入任务等待队列中等待核心线程的执行;当任务等待队列中的线程也已满了之后,通过判断现在的线程池数量是否已经超过了规定的最大线程池数量,若没有,则新建线程去执行任务,若已经相等了,则调用拒绝策略去处理线程任务;假设现在已经没什么任务了,线程池中的线程都处于比较空闲的状态了,则超过指定的keepAliveTime后,额外新建的线程(超出CorePool线程数的线程)则会被销毁。



线程池的生命周期

5种状态

  • Running:运行中,可以正常处理任务
  • Shutdown(执行Shutdown()方法):不再继续接受新的任务,只执行正在进行中的任务和等待队列中的任务,执行完之后进入Tidying状态
  • Stop(执行ShutdownNow()方法):不再继续接受新的任务,也不执行正在进行中的任务和等待队列中的任务,马上进入Tidying状态
  • Tidying:在进入此状态后调用terminated()方法,进入Terminated状态
  • Terminated:在terminated()执行完毕后进入该状态,在此状态中默认什么都没有做

与线程的生命周期区别?

线程生命周期的5种状态
  • 新生——new
  • 就绪——准备执行
  • 运行——running
  • 阻塞——wait()
  • 消亡——线程执行完毕

关注我的公众号、了解更多有关于学习的文章

微信公众号:艾迪威姆 / PositiveEddie

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值