资讯专栏INFORMATION COLUMN

java多线程(6)线程池

RiverLi / 2824人阅读

摘要:虽然使用很方便,但是建议大家使用已经设定的几种线程池无界线程池,可以进行线程自动回收固定大小线程池和单个后线程,它们满足大部分的场景需求。固定大小线程池和有些类似,只不过从单线程变成可以指定线程数量,依旧为无限。

池的概念在java中也是常见,还有连接池、常量池等,池的作用也是类似的,对于对象、资源的重复利用,减小系统开销,提升运行效率。

线程池的主要功能:
1.减少创建和销毁线程的次数,提升运行性能,尤其是在大量异步任务时
2.可以更合理地管理线程,如:线程的运行数量,防止同一时间大量任务运行,导致系统崩溃

demo

先举个demo,看看使用线程池的区别,线程池:

AtomicLong al = new AtomicLong(0l);
            Long s1 = System.currentTimeMillis();
            ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 100000, 100, TimeUnit.SECONDS, new LinkedBlockingDeque(20000));
            for(int i=0;i<20000;i++){
                pool.execute(new Runnable() {
                    
                    @Override
                    public void run() {
                        try {
                            al.incrementAndGet();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            pool.shutdown();
            pool.awaitTermination(1, TimeUnit.HOURS);
            System.out.println("耗时:"+(System.currentTimeMillis()-s1));
            System.out.println("AtomicLong="+al.get());

结果:

耗时:224
AtomicLong=20000

非线程池:

AtomicLong al = new AtomicLong(0l);            
            Long s1 = System.currentTimeMillis();
            for(int i=0;i<20000;i++){
                Thread t = new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                        try {
                            al.incrementAndGet();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
                t.start();
            }
            while(true){
                if(al.get()==20000){
                    System.out.println("耗时:"+(System.currentTimeMillis()-s1));
                    System.out.println("AtomicLong="+al.get());
                    break;
                }
                Thread.sleep(1);
            }

结果:

耗时:2972
AtomicLong=20000

从耗时看2者相差了13倍,差距还是挺大的,可见有大量异步任务时使用线程池能够提升性能。

线程池的主要参数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

线程池的主要参数有这6个:

corePoolSize:核心池的大小,核心池的线程不会被回收,没有任务就处于空闲状态。

maximumPoolSize:线程池最大允许的线程数,

keepAliveTime:当线程数超过corePoolSize时,但小于等于maximumPoolSize,会生成‘临时’线程,‘临时’线程执行完任务不会马上被回收,如果在keepAliveTime时间内,还没有新任务的话,才会被回收。

unit:keepAliveTime的单位。

workQueue:当前线程数超过corePoolSize大小时,新任务会处在等待状态,存在workQueue中。

threadFactory:生成新线程的工厂。

handler:当线程数超过workQueue的上限时,新线程会被拒绝,这个参数就是新线程被拒绝的方式。

其中corePoolSize和maximumPoolSize相对难理解,再详细说明:
1、池中线程数小于corePoolSize,新任务都不排队而是直接添加新线程

2、池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程

3、池中线程数大于等于corePoolSize,workQueue已满,但是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务

4、池中线程数大于大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务

因此maximumPoolSize、corePoolSize不宜设置过大,否则会造成内存、cpu过载的问题,workQueue而尽量可以大一些。

Executors

虽然ThreadPoolExecutor使用很方便,但是建议大家使用jdk已经设定的几种线程池:
Executors.newCachedThreadPool()(无界线程池,可以进行线程自动回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSingleThreadExecutor()(单个后线程),它们满足大部分的场景需求。

1.newSingleThreadExecutor 单线程线程池
看下它的实现方式:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    }

把corePoolSize设为1,而workQueue大小设为无限大,因此永远只有一个线程在执行任务,新任务都在workQueue中等待。

2.newFixedThreadPool 固定大小线程池

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }

和newSingleThreadExecutor 有些类似,只不过从单线程变成可以指定线程数量,workQueue依旧为无限。
3.newCachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

newCachedThreadPool所有新任务都会被立即执行,corePoolSize 设置为0,maximumPoolSize设置为整数最大值,所有线程超过60秒未被使用,就会被销毁。

workQueue

当前线程数超过corePoolSize大小时,新任务会处在等待状态,存在workQueue中。workQueue的实现方式也就是任务的等待策略,分为3种:有限队列、无限队列、直接提交。

谈谈前2种,jdk使用了LinkedBlockingQueue而非ArrayBlockingQueue,有限队列的优势在于对资源和效率更定制化的配置,但是对比无限队列缺点也很明显:

1).增加开发难度。需要考虑到corePoolSize ,maximumPoolSize,workQueue,3个参数大小,既要使任务高效地执行,又要避免任务超量的问题,对于开发和测试的复杂度提高很多。
2).防止业务突刺。在互联网应用业务量暴增也是必须考虑的问题,在使用无限队列时,虽然执行可能慢一点,但是能保证执行到。使用有限队列,会出现任务拒绝的问题。
综上考虑,更建议使用无限队列,尤其是对于多线程不是很熟悉的朋友们。

拒绝策略

所谓拒绝策略之前也提到过了,任务太多,超过maximumPoolSize了怎么办?当然是接不下了,接不下那只有拒绝了。拒绝的时候可以指定拒绝策略,也就是一段处理程序。

决绝策略的父接口是RejectedExecutionHandler,JDK本身在ThreadPoolExecutor里给用户提供了四种拒绝策略,看一下:

1、AbortPolicy

直接抛出一个RejectedExecutionException,这也是JDK默认的拒绝策略

2、CallerRunsPolicy

尝试直接运行被拒绝的任务,如果线程池已经被关闭了,任务就被丢弃。

3、DiscardOldestPolicy

移除最晚的那个没有被处理的任务,然后执行被拒绝的任务。同样,如果线程池已经被关闭了,任务就被丢弃了

4、DiscardPolicy

悄悄地拒绝任务

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76376.html

相关文章

  • 跟着阿里p7一起学java高并发 - 第18天:玩转java线程,这一篇就够了

    摘要:高并发系列第篇文章。简单的说,在使用了线程池之后,创建线程变成了从线程池中获取一个空闲的线程,然后使用,关闭线程变成了将线程归还到线程池。如果调用了线程池的方法,线程池会提前把核心线程都创造好,并启动线程池允许创建的最大线程数。 java高并发系列第18篇文章。 本文主要内容 什么是线程池 线程池实现原理 线程池中常见的各种队列 自定义线程创建的工厂 常见的饱和策略 自定义饱和策略 ...

    AdolphLWQ 评论0 收藏0
  • 想进大厂?50个线程面试题,你会少?(一)

    摘要:下面是线程相关的热门面试题,你可以用它来好好准备面试。线程安全问题都是由全局变量及静态变量引起的。持有自旋锁的线程在之前应该释放自旋锁以便其它线程可以获得自旋锁。 最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员...

    wow_worktile 评论0 收藏0
  • (十七)java线程之ThreadPoolExecutor

    摘要:本人邮箱欢迎转载转载请注明网址代码已经全部托管有需要的同学自行下载引言在之前的例子我们要创建多个线程处理一批任务的时候我是通过创建线程数组或者使用线程集合来管理的但是这样做不太好因为这些线程没有被重复利用所以这里要引入线程池今天我们就讲线程 本人邮箱: 欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kcogithub: https://github...

    wpw 评论0 收藏0
  • Java 并发方案全面学习总结

    摘要:进程线程与协程它们都是并行机制的解决方案。选择是任意性的,并在对实现做出决定时发生。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。此线程池支持定时以及周期性执行任务的需求。 并发与并行的概念 并发(Concurrency): 问题域中的概念—— 程序需要被设计成能够处理多个同时(或者几乎同时)发生的事件 并行(Parallel...

    mengera88 评论0 收藏0
  • JAVA 线程和并发基础面试问答

    摘要:多线程和并发问题是技术面试中面试官比较喜欢问的问题之一。线程可以被称为轻量级进程。一个守护线程是在后台执行并且不会阻止终止的线程。其他的线程状态还有,和。上下文切换是多任务操作系统和多线程环境的基本特征。 多线程和并发问题是 Java 技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(...

    dreamans 评论0 收藏0

发表评论

0条评论

RiverLi

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<