为编程爱好者分享易语言教程源码的资源网
好用的代理IP,游戏必备 ____广告位招租____ 服务器99/年 ____广告位招租____ ____广告位招租____ 挂机,建站服务器
好用的代理IP,游戏必备 ____广告位招租____ 服务器低至38/年 ____广告位招租____ ____广告位招租____ 挂机,建站服务器

网站首页 > 网络编程 > 其它综合 正文

多线程-005-线程池ThreadPoolExecutor正确使用与原理

三叶资源网 2022-10-09 19:18:50 其它综合 205 ℃ 0 评论

线程池

线程是一个重量级的对象,应该避免频繁创建和销毁。


线程池是一种生产者 - 消费者模式,目的:防止频繁创建线程和销毁线程带来的性能开销。

Java 提供的线程池相关的工具类中,最核心的是ThreadPoolExecutor

  • corePoolSize:表示线程池保有的最小线程数。
  • maximumPoolSize:表示线程池创建的最大线程数。当项目很忙时,就需要加人,但是也不能无限制地加,最多就加到 maximumPoolSize 个人。当项目闲下来时,就要撤人了,最多能撤到 corePoolSize 个人。
  • keepAliveTime & unit:一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。
  • workQueue:工作队列
  • threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字
  • handler:自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。ThreadPoolExecutor 已经提供了以下 4 种策略。
    1. CallerRunsPolicy:提交任务的线程自己去执行该任务。
    2. AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
    3. DiscardPolicy:直接丢弃任务,没有任何异常抛出。
    4. DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列

Java 在 1.6 版本还增加了 allowCoreThreadTimeOut(boolean value) 方法,它可以让所有线程都支持超时,这意味着如果项目很闲,就会将项目组的成员都撤走。

使用借助了 Jodd 类库的 ThreadFactoryBuilder 方法来构造一个线程工厂,实现线程池线程的自定义命名
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
        2, 5,
        5, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(10),
        new ThreadFactoryBuilder().setNameFormat("demo-threadpool-%d").get(),
        new RejectedExecutionHandler(){
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                try {
                    // 队列满了就让放入入任务的线程等待,直到队列没满
                    while (!e.isShutdown() && e.getQueue().size()==10){
                        log.info("处理不过来了,暂停1秒...");
                        TimeUnit.SECONDS.sleep(1);
                    }
                    e.getQueue().add(r);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        });
// 创建线程池,不借助工具类,指定线程名字
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 0,
        TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), generateThreadFactory("test-%d"),
        new ThreadPoolExecutor.AbortPolicy());
private static ThreadFactory generateThreadFactory(String nameFormat){
    AtomicLong count =  new AtomicLong(0L);
    return (runnable) -> {
        Thread thread = new Thread(runnable);
        thread.setName(String.format(nameFormat, count.getAndIncrement()));
        return thread;
    };
}

// 打印线程池状态 ,每秒输出一次线程池的基本内部信息,包括线程数、活跃线程数、完成了多少任务,以及队列中还有多少积压任务等信息
private void printStats(ThreadPoolExecutor threadPool) {
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
        log.info("=========================");
        log.info("Pool Size: {}", threadPool.getPoolSize());
        log.info("Active Threads: {}", threadPool.getActiveCount());
        log.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount());
        log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
        log.info("=========================");
    }, 0, 1, TimeUnit.SECONDS);
}
// 允许核心线程回收
threadPool.allowCoreThreadTimeOut(true);

线程池工作原理:

1,将任务提交到队列

2,线程从队列获取任务,执行

3,如果运行的线程少于 corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;

4,如果线程池中的线程数量大于等于 corePoolSize 且小于 maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务;

5,如果运行的线程数量大于等于maximumPoolSize,这时如果workQueue已经满了,则通过handler所指定的策略来处理任务;

6,所以,任务提交时,判断的顺序为线程小于 corePoolSize,创建新的线程来处理,等于corePoolSize,将任务提交到 workQueue(工作队列),工作队列满了, 增加线程到 maximumPoolSize。 工作队列满了,最大线程满了,就执行


注意:

1,任何时候,都应该为自定义线程池指定有意义的名称,以方便排查问题。当出现线程数量暴增、线程死锁、线程占用大量 CPU、线程执行出现异常等问题时,我们往往会抓取线程栈。此时,有意义的线程名称,就可以方便我们定位问题。

2,不建议使用 Executors 的最重要的原因是:Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列。

3,如果任务在执行的过程中出现运行时异常,会导致执行任务的线程终止。所以对业务逻辑建议要进行try catch处理。

4,优化:声明线程池后立即调用 prestartAllCoreThreads 方法,来启动所有核心线程;传入 true 给 allowCoreThreadTimeOut 方法,来让线程池在空闲的时候同样回收核心线程。


思考:Java 线程池是先用工作队列来存放来不及处理的任务,满了之后再扩容线程池。当我们的工作队列设置得很大时,最大线程数这个参数显得没有意义,因为队列很难满,或者到满的时候再去扩容线程池已经于事无补了。

如何使线程池,线程数达到corePoolSize时,先增加线程,达到maximumPoolSize,再往队列加入任务呢?

1,Tomcat 线程池对这方面进行了优化可以参考

(1)https://github.com/apache/tomcat/blob/a801409b37294c3f3dd5590453fb9580d7e33af2/java/org/apache/tomcat/util/threads/ThreadPoolExecutor.java

(2)https://segmentfault.com/a/1190000023038647

2,自己写思路(1)重写队列的 offer 方法,造成这个队列已满的假象 (2)达到了最大线程后势必会触发拒绝策略,实现一个自定义的拒绝策略处理程序, 把任务真正插入队列。


线程池关闭:https://www.cnblogs.com/qingquanzi/p/9018627.html

线程中断

当我们调用线程的interrupt方法,它有两个作用:

1、如果此线程处于阻塞状态(比如调用了wait方法,io等待),则会立马退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做一定的处理,然后让线程退出。

2、如果此线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以线程要在适当的位置通过调用isInterrupted方法来查看自己是否被中断,并做退出操作。


线程池提供了两个关闭方法

shutdownNow:线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。将队列里还没有执行的任务放到列表里,返回给调用方。

  • shutdownNow方法的执行逻辑:将线程池状态修改为STOP,然后调用线程池里的所有线程的interrupt方法。将队列里还没有执行的任务放到列表里,返回给调用方。
  • 因为调用了中断interrupt方法,线程如果处于阻塞状态,就抛出异常,被捕获。由于STOP状态值是大于SHUTDOWN状态,STOP也大于等于STOP,不管任务队列是否为空,都会进入if语句从而返回null,线程退出。

shutdown:线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。

  • 是将线程池的状态修改为SHUTDOWN状态,然后调用interruptIdleWorkers方法,来中断空闲的线程。
  • if (!t.isInterrupted() && w.tryLock()) 运行在w.lock和w.unlock之间的线程将因为加锁失败,而不会被调用interrupt方法,换句话说,就是正在执行线程池里任务的线程不会被中断。
  • 不管是被调用了interrupt的线程还是没被调用的线程,什么时候退出呢?,这就要看getTask方法的返回是否为null了。
  • 由于线程池被shutdown方法修改为SHUTDOWN状态,SHUTDOWN大于等于SHUTDOWN成立没问题,但是SHUTDOWN不在大于等于STOP状态,所以只有队列为空,getTask方法才会返回null,导致线程退出。


调用完shutdownNow和shuwdown方法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调用awaitTermination方法进行同步等待。


线程池里的线程在什么情况下才会彻底退出?

1,程序出异常。

2,task = null && getTask()方法返回null, 线程退出

小结:

1,使用shutdownNow如果线程池里的任务处于阻塞状态,则会导致报错(如果任务里没有捕获InterruptedException异常),如果线程daemon=false , 主线程报错,线程池没有关闭。

2,使用shuwdown方法关闭线程池时,一定要确保任务里不会有永久阻塞等待的逻辑,否则线程池就关闭不了。

3,shutdownNow和shuwdown调用完,线程池并不是立马就关闭了,要想等待线程池关闭,还需调用awaitTermination方法来阻塞等待。

4,注意线程的daemon属性,为true时,主线程执行完,线程池会自动关闭。为false时,主线程执行完,线程池线程仍在,需要手动关闭线程池。

来源:三叶资源网,欢迎分享,公众号:iisanye,(三叶资源网⑤群:21414575

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

百度站内搜索
关注微信公众号
三叶资源网⑤群:三叶资源网⑤群

网站分类
随机tag
注册美团CryptoJS加密模块网络相关源码易语言聊天室源码JS教程获取本机信息图像处理销售记录系统多线程注册HttpWatch破解版PNG素材炫彩界面Pc微信Hook源码QQ采集Unicode自动更新二维数组IP地址定位留言板加群链接
最新评论