手机看天气

扫码下载app,天气随时看

扫码下载app,天气随时看
收藏网页

您使用的浏览器版本过低!

可能无法正常浏览,您可以选择

不懂装懂网 > 直接下载更新IE浏览器 >

扫码码下载APP,天气随时看

安卓下载 App Store

发布

本篇文章给大家谈谈线程池拒绝策略,以及线程池拒绝策略什么时候执行对应的知识点,希望对各位有所帮助,不要忘了收藏本站!

内容导航:
  • 线程与线程池
  • 线程池工作原理
  • 线程池的四种创建方式及区别
  • 线程池的一些面试题
  • 线程池七大核心参数
  • 合理使用线程池以及线程变量

Q1:线程与线程池

1、线程的状态。5个。

2、实现线程的方法,及其区别。2种:Runnable、Thread(+2种:Callable、FutureTask)。

3、start()和run()的区别。

4、Thread.sleep()和Thread.yield()区别

yield,音标 /jild/。线程的礼让,该线程退回到就绪状态(然后所有的就绪的线程凭借优先级抢资源)。

sleep,线程的阻塞(当阻塞时间结束,线程转入就绪状态)。

5、wait和sleep的区别

1)wait是object的方法,sleep是thread的静态方法;

2)wait需要在synchronized范围内使用,否则抛错 Exception in thread "main" java.lang.IllegalMonitorStateException ,而sleep则不需要;

3)wait是对象监听器的线程的等待,当该对象wait时,当前线程进入等待,其notify方法是随机唤起一个(等待该对象监听器的)线程;sleep是当前线程的沉睡,该线程的对象锁还是持有的;

4)wait出让系统资源,进入线程池中等待;sleep不会出让锁。二者都会让出CPU。

5、用户线程(user Thread)和守护线程(daemon Thread)的区别。

1)守护线程的区别在于thread.setDaemon(true),设置了就是守护线程,且必须在start()之前设置。

2)守护线程依赖于用户线程,没有用户线程,守护线程不存在。即当用户线程运行完毕,此时不管守护线程是否运行或运行完毕,立即停止。

6、线程调用的两种方式:

1)直接使用start()方法(在主方法中显式迭代调用或者构造方法中,便于外部隐式调用);

2)使用Executor来调用(CachedThreadPool()或者FixedThreadPool())。

两种方法的区别是:Executor执行线程都是隐式的。而且在构造方法中调用start()方法对于多线程是不安全的,而Executor则不会。

7、停止一个运行中线程的方法:

1)interrupt方法;

2)使用退出标志;

3)stop,但不建议(J8废除,原因是可能导致数据不一致)。

关于stop方法,参考 https://blog.csdn.net/a158123/article/details/78776145 。

6、Executor调用线程的两种方式的区别:

newCachedThreadPool()会为每一个任务都分配线程;

newFixedThreadPool(long)会限定可使用的线程数量,在前面的任务执行完之后,会将空线程分配给其他的任务。

7、在并发时,一个任务不能依赖于另一个任务,因为任务的关闭顺序无法保证。解决:1.依赖于非任务对象(volatile变量)来解决。2.锁。

8、锁的方式:2种,synchronize和Lock。区别在于Lock更加细粒度,比如锁的尝试获取,锁的锁定时间。

9、线程池的状态:5个。

1.running

2.shutdown

3.stop

4.tidying(当workQueue为0时,进入该状态)

5.terminated

10、shutdown和stop的区别。

二者都有线程池停止之意,且都不接收新线程了。但shutdown会处理掉已接收和正在执行的线程,而stop会中断所有的已接收和正在执行的线程。

11、threadPoolExecutor的参数含义。

corePoolSize:核心线程数。即最小存活线程数。

maximunPoolSize:最大线程数。

keepAliveTime:当线程数大于核心线程是,线程的存活时间。

unit:keepAliveTime的单位。

workQueue:工作队列。在线程执行前,线程会放在此处。

threadFactory:线程创建工厂(有一个默认工厂)。

handler:拒绝策略。当多余的线程请求时,执行的策略。默认是拒绝策略。

ps:关于workQueue:当需要使用一个线程时,会先看核心线程有无空闲线程,若有,则直接使用,没有,则创建并放在队列中等待被使用;当线程用完时,也会放在队列中,等待一会,实在没人用且已达到核心数时,会消亡该线程。

newSingleThreadExecutor() 和 newFixedThreadPool() 都是用的LinkedBlockingQueue队列,而 newCachedThreadPool() 用的是SynchronousQueue队列。

在 newSingleThreadExecutor() 中,如果前一个线程出异常了,那么我就执行下一个线程,不会出现停止,而其他的线程池会导致停止。

在 newFixedThreadPool() 中keepAliveTime是0,在 newCachedThreadPool() 中keepAliveTime是60s。

12、线程的循环调用(如每隔5秒调用线程):

这是一个初始化后延迟1秒,每隔5秒执行任务(秒单位共享)。

这是一个初始化后延迟1毫秒,每隔5秒执行任务。默认单位为毫秒。

参考: Java多线程线程池(4)--线程池的五种状态 。

Q2:线程池工作原理

管理线程,当线程执行完当前任务,不会死掉而是 会从队列里面取

1.降低系统资源消耗。通过复用已存在的线程,降低线程创建和销毁造成的消耗;

2.提高响应速度。当有任务到达时,无需等待新线程的创建便能立即执行;

3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳定性,使用线程池可以进行对线程进行统一的分配、调优和监控。

本文主要是围绕 ThreadPoolExecutor(线程池框架的核心类)的构造方法参数 展开:

1.corePoolSize

线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

2.maximumPoolSize

额外最大线程数。上面说到任务数足够多,且使用的是有界队列,如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,首先从队列里面取,如果队列里面的消息执行完毕,等下一定时间,额外线程自动销毁。

3.keepAliveTime

线程空闲时的存活时间。默认情况下,可以理解成额外最大线程数没活干了,额外线程线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

4.unit

keepAliveTime参数的时间单位。

5.workQueue

任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。

一般来说,这里的BlockingQueue有以下三种选择:

* SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。

* LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量为Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。

* ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。

6.threadFactory

线程工厂,创建新线程时使用的线程工厂。

7.handler

任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:

*AbortPolicy :丢弃任务并抛出RejectedExecutionException异常,默认策略;

*CallerRunsPolicy :由调用execute方法的线程执行该任务;

*DiscardPolicy :丢弃任务,但是不抛出异常;

*DiscardOldestPolicy :丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。

* 当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

总结下上诉参数:假设corePoolSize为10 ,maximumPoolSize为10,线程空闲时的存活时间为60s,队列采用的是有界队列ArrayBlockingQueue 设置阈值200,使 用拒绝策略 , 当前2000个任务提交过来 流程如下图

参数案例描述:

         当前2000笔 任务进来,10个核心线程去处理,剩下的1990任务队列里面放200个。剩下的1790个任务。队列塞满会去创建10个额外线程和核心线程一起去 去执行剩下的1780个任务。 当还有剩下任务处理不了就会触发任务拒绝策略 。  

       当前220笔 任务进来,10个核心线程去处理,剩下的210任务队列里面放200个。剩下的10个任务。队列塞满会去创建10个额外线程 去执行队列放不下的任务。 当额外线程和核心线程处理完队列里面的队列。没有任务可执行时,额外线程会等待我们设置的keepAliveTime,还是没有任务的情况下,就会被回收了 。

以上是绝对理想的状况下。

由参数可知 核心线程 和额外线程值是相同的,额外线程被回收时间是0,采用的是无界队列。默认采用的拒绝策略为 AbortPolicy。分析得 核心线程和额外线程处理不过来得情况,会一直往队列里面放任务。

可能存在的问题:队列过大 导致内存溢出 OOM

当任务量足够大,超过队列。交由额外线程处理。就会创建过多线程。

可能存在问题:特殊场景下,线程过多可能会导致系统奔溃。cpu负载过高。

1.具体解决方案 根据业务系统而定:

         华瑞批量查证举例:定时任务CZJZRW001每隔2min 轮询一次 会从业务表verifycationTask 中 查询出待处理和处理中的状态的任务 根据表中的查证类型 分流到具体的 反欺诈异步查证 ,还款查证,充值查证,贷款查证 。 具体查证根据处理结果更新verifycationTask表查证状态。处理成功 或者失败的定时任务无法再次轮询。这样就不需要考虑以上场景。使用线程池的情况下核心线程,额外线程处理不过来且队列已满使用DiscardPolicy拒绝不抛异常策略 ,即可满足该业务场景。类结构如下图

2.思路

可以实现 RejectedExecutionHandler接口 自定义拒绝策略  将被拒绝的任务信息缓存到磁盘,等待线程池负载较低 从磁盘读取重新提交到任务里面去执行

Q3:线程池的四种创建方式及区别

核心线程数为0,非核心线程数为MAX_VALUE,

队列不存储值,总认为队列是满的,所以每次执行任务时都会创建非核心线程,非核心线程空闲了超过60秒(默认),就会自动回收。

2.newfixedThreadPool 创建定长的线程池

在达到长度之前,每提交一个任务都会创建一个线程,如果达到线程池最大数量,则提交到队列中,在空闲的时候也不会自动回收线程

核心线程数为参数传入,非核心线程数和核心线程数一样,

队列为无界队列,资源有限的时候容易引起OOM.

与newSingledThreadPool不同的是核心线程数不为1.

3.newSingledThreadPool 创建单一线程执行。

只有一个线程按顺序执行任务,如果这个线程出现异常结束,会有另一个线程取代并按顺序执行。

corepoolsize 核心线程数为1 ,非核心线程数为1 ,

队列为无界队列,

单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4.newScheduedThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。如果延迟3秒执行或每隔3秒执行一次

核心线程数为 参数设定,非核心线程数为MAX_VALUE

定义了一个DelayedWorkQueue,它是一个有序队列,会通过每个任务按照距离下次执行时间间隔的大小来排序;

线程池执行逻辑说明:

判断核心线程数是否已满,核心线程数大小和corePoolSize参数有关,未满则创建线程执行任务

若核心线程池已满,判断队列是否满,队列是否满和workQueue参数有关,若未满则加入队列中

若队列已满,判断线程池是否已满,线程池是否已满和maximumPoolSize参数有关,若未满创建线程执行任务

若线程池已满,则采用拒绝策略处理无法执执行的任务,拒绝策略和handler参数有关

拒绝策略

拒绝策略 => 默认采用的是AbortPolicy拒绝策略,直接在程序中抛出RejectedExecutionException异常【因为是运行时异常,不强制catch】,这种处理方式不够优雅。处理拒绝策略有以下几种比较推荐:

在程序中捕获RejectedExecutionException异常,在捕获异常中对任务进行处理。针对默认拒绝策略

使用CallerRunsPolicy拒绝策略,该策略会将任务交给调用execute的线程执行【一般为主线程】,此时主线程将在一段时间内不能提交任何任务,从而使工作线程处理正在执行的任务。此时提交的线程将被保存在TCP队列中,TCP队列满将会影响客户端,这是一种平缓的性能降低

自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可

如果任务不是特别重要,使用DiscardPolicy和DiscardOldestPolicy拒绝策略将任务丢弃也是可以

public class ThreadTest {

//ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

//

//scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

//public void run() {

//System.out.println("delay 1 seconds, and excute every 3 seconds");

//

//}

//

//}, 1, 3, TimeUnit.SECONDS);

}

Q4:线程池的一些面试题

1,为什么要用线程池,优势

(1)降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2) 提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行。

(3)  提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

1.1常用方式

那java中是怎样实现的线程池呢?是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个接口或类,它们都是JUC包下的。 java.util.concurrent.Executors类是Executor的辅助类,类似于java中操作数组的辅助类java.util.Arrays,以及操作集合的java.util.Collections类

1.2:Executors类中的主要三个方法

线程安全的队列:staticQueue queue = new ConcurrentLinkedQueue<String>();

(1) 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中的等待,它创建的线程池corePoolSize和maximnumPoolSize是相等的,它使用的是LinkedBlockingQueue;

源码如下:

  public static ExecutorService newFixedThreadPool(int nThreads) {

        return new ThreadPoolExecutor(nThreads, nThreads,

                                      0L, TimeUnit.MILLISECONDS,

                                      new LinkedBlockingQueue<Runnable>());

    }

(2)Executors#newSingleThreadExecutor

    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行,它将corePoolSize和maximnumPoolSize都设置为1,它也使用的是LinkedBlockingQueue;

源码:

public static ExecutorService newSingleThreadExecutor() {

        return new FinalizableDelegatedExecutorService

            (new ThreadPoolExecutor(1, 1,

                                    0L, TimeUnit.MILLISECONDS,

                                    new LinkedBlockingQueue<Runnable>()));

    }

(3)Executors#newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。,它将corePoolSize设置为0,将maximnumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,当前线程空闲超过60秒,就销毁线程;

源码:

public static ExecutorService newCachedThreadPool() {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue<Runnable>());

    }

2,线程池的重要参数:

源码:

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

参数:

corePoolSize

    线程池中的常驻核心线程数,在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

maximumPoolSize

    线程池能够容纳同时执行的最大线程数,此值必须大于等于1。

keepAliveTime

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

unit

    keepAliveTime的单位。

workQueue

    任务队列,被提交但尚未被执行的任务。

threadFactory

    表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。

handler

    拒绝策略,表示当队列满了,再也塞不下新任务了,同时,工作线程大于等于线程池的最大线程数,无法继续为新任务服务,这时候我们就需要拒绝策略机制合理的处理这个问题,默认会抛异常, 那拒绝策略有哪些呢,我们继续往下看。

JDK内置的接口:RejectedExcutionHandle

AbortPolicy(默认)

    直接抛出java.util.concurrent.RejectedExecutionException异常阻止系统正常运行,这种方式显然是不友好的。

CallerRunsPolicy

    "调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

DiscardOldestPolicy

    抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

DiscardPolicy

    直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种解决方案。

    具体选择哪一种的拒绝策略,也是看自己的系统需求了;

3,底层工作原理

(1).在创建了线程池后,等待提交过来的任务请求

 (2).当调用execute()方法添加一个请求任务时,线程池会做如下判断

            2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务

            2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列

            2.3 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务

            2.4 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行

    (3). 当一个线程完成任务时,它会从队列中取下一个任务来执行

    (4). 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断

            4.1 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉

            4.2 所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小

创建线程池时,配置多少线程数是合理的:

(1)CPU密集型:CPU核数+1个线程的线程池(CPU密集任务只有在真正的多核CPU上才可能得到加速)

(2)IO密集型:O密集型时,大部分线程都阻塞,故需要多配置线程数,CPU核数/1-阻塞系数 阻塞系数在0.8至0.9之间。例如4核,取个乐观值0.9,可达到40个线程左右

------------------------------------------------------------------------

--------------------------------

阿里巴巴开发手册上:出自于生产时间来说:

1,线程资源必须通过线程池提供,不能够在应用中自行创建线程;

2,线程池不允许使用Executors去创建,而是使用ThreadPoolExecutor的方式,

这样可以明确线程池的规则,规避资源耗尽的风险;

---------------------------

SpringBoot 自定义线程池:

1,application.yml配置:

task:

pool:

corePoolSize:5#设置核心线程数

maxPoolSize:20#设置最大线程数

keepAliveSeconds:300#设置线程活跃时间(秒)

queueCapacity:50#设置队列容量

2,线程池配置属性类:

importorg.springframework.boot.context.properties.ConfigurationProperties;

/**

* 线程池配置属性类

*/

@ConfigurationProperties(prefix ="task.pool")

publicclassTaskThreadPoolConfig{

privateintcorePoolSize;

privateintmaxPoolSize;

privateintkeepAliveSeconds;

privateintqueueCapacity;

    ...getter and setter methods...

}

3,启动类上加上异步支持:

@EnableAsync

@EnableConfigurationProperties({TaskThreadPoolConfig.class} )// 开启配置属性支持

4,自定义线程池:

/**

* 创建线程池配置类

*/

@Configuration

public class TaskExecutePool {

    @Autowired

    private TaskThreadPoolConfig config;

    /**

   * 1.这种形式的线程池配置是需要在使用的方法上面@Async("taskExecutor"),

   * 2.如果在使用的方法上面不加该注解那么spring就会使用默认的线程池

   * 3.所以如果加@Async注解但是不指定使用的线程池,又想自己定义线程池那么就可以重写spring默认的线程池

   * 4.所以第二个方法就是重写默认线程池

   * 注意:完全可以把线程池的参数写到配置文件中

   */

    @Bean

    public Executor taskExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //核心线程池大小

        executor.setCorePoolSize(config.getCorePoolSize());

        //最大线程数

        executor.setMaxPoolSize(config.getMaxPoolSize());

        //队列容量

        executor.setQueueCapacity(config.getQueueCapacity());

        //活跃时间

        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());

        //线程名字前缀

        executor.setThreadNamePrefix("TaskExecutePool-");

        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务

        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

       // 等待所有任务结束后再关闭线程池

        executor.setWaitForTasksToCompleteOnShutdown(true);

        executor.initialize();

        return executor;

    }

}

测试:

import io.swagger.annotations.Api;

import io.swagger.annotations.ApiOperation;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;

/**

* @author qijx

*/

@Api(description = "测试控制类11111")

@RestController

@RequestMapping("/threadPoolController1")

public class ThreadPoolController1 {

        @Autowired

        private ThreadPoolService1 threadPoolService;

        @ApiOperation(value = "测试方法")

        @ResponseBody

        @RequestMapping(value = "/test",method = RequestMethod.GET)

        public String threadPoolTest() {

            threadPoolService.executeAsync();

            return "hello word!";

        }

}

第二种方法:重写springboot线程池:

**

* 原生(Spring)异步任务线程池装配类,实现AsyncConfigurer重写他的两个方法,这样在使用默认的

*  线程池的时候就会使用自己重写的

*/

@Slf4j

@Configuration

public class NativeAsyncTaskExecutePool implements AsyncConfigurer{

    //注入配置类

    @Autowired

    TaskThreadPoolConfig config;

    @Override

    public Executor getAsyncExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //核心线程池大小

        executor.setCorePoolSize(config.getCorePoolSize());

        //最大线程数

        executor.setMaxPoolSize(config.getMaxPoolSize());

        //队列容量

        executor.setQueueCapacity(config.getQueueCapacity());

        //活跃时间

        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());

        //线程名字前缀

        executor.setThreadNamePrefix("NativeAsyncTaskExecutePool-");

        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务

        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 等待所有任务结束后再关闭线程池

        executor.setWaitForTasksToCompleteOnShutdown(true);

        executor.initialize();

        return executor;

    }

    /**

   *  异步任务中异常处理

   * @return

   */

    @Override

    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {

        return new AsyncUncaughtExceptionHandler() {

            @Override

            public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {

                log.error("=========================="+arg0.getMessage()+"=======================", arg0);

                log.error("exception method:"+arg1.getName());

            }

        };

    }

}

测试:

/**

* @author qijx

*/

@Service

public class ThreadPoolService2 {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPoolService2.class);

    /**

   * @Async该注解不需要在指定任何bean

   */

    @Async

    public void executeAsync() {

        logger.info("start executeAsync");

        try {

            System.out.println("当前运行的线程名称:" + Thread.currentThread().getName());

            Thread.sleep(1000);

        } catch (Exception e) {

            e.printStackTrace();

        }

        logger.info("end executeAsync");

    }

}

------------------------------------

Q5:线程池七大核心参数

线程池七大核心参数如下所示:


一、corePoolSize 线程池核心线程大小


线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。


二、maximumPoolSize 线程池最大线程数量


当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列(后面会介绍)中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。


三、keepAliveTime 空闲线程存活时间


一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。


四、unit 空闲线程存活时间单位


空闲线程存活时间单位是keepAliveTime的计量单位。


五、workQueue 工作队列


新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。


六、threadFactory 线程工厂


创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。


七、handler 拒绝策略


当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的。


线程池的优势


1、线程和任务分离,提升线程重用性;


2、控制线程并发数量,降低服务器压力,统一管理所有线程;


3、提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间。

Q6:合理使用线程池以及线程变量

背景

随着计算技术的不断发展,3纳米制程芯片已进入试产阶段,摩尔定律在现有工艺下逐渐面临巨大的物理瓶颈,通过多核处理器技术来提升服务器的性能成为提升算力的主要方向。

在服务器领域,基于java构建的后端服务器占据着领先地位,因此,掌握java并发编程技术,充分利用CPU的并发处理能力是一个开发人员必修的基本功,本文结合线程池源码和实践,简要介绍了线程池和线程变量的使用。

线程池概述

线程池是一种“池化”的线程使用模式,通过创建一定数量的线程,让这些线程处于就绪状态来提高系统响应速度,在线程使用完成后归还到线程池来达到重复利用的目标,从而降低系统资源的消耗。

总体来说,线程池有如下的优势:

线程池的使用

在java中,线程池的实现类是ThreadPoolExecutor,构造函数如下:

可以通过 new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,handler)来创建一个线程池。

在构造函数中,corePoolSize为线程池核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程超时也会回收。

在构造函数中,maximumPoolSize为线程池所能容纳的最大线程数。

在构造函数中,keepAliveTime表示线程闲置超时时长。如果线程闲置时间超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。

在构造函数中,timeUnit表示线程闲置超时时长的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

在构造函数中,blockingQueue表示任务队列,线程池任务队列的常用实现类有:

在构造函数中,threadFactory表示线程工厂。用于指定为线程池创建新线程的方式,threadFactory可以设置线程名称、线程组、优先级等参数。如通过Google工具包可以设置线程池里的线程名:

在构造函数中,rejectedExecutionHandler表示拒绝策略。当达到最大线程数且队列任务已满时需要执行的拒绝策略,常见的拒绝策略如下:

ThreadPoolExecutor线程池有如下几种状态:

线程池提交一个任务时任务调度的主要步骤如下:

核心代码如下:

Tomcat 的整体架构包含连接器和容器两大部分,其中连接器负责与外部通信,容器负责内部逻辑处理。在连接器中:

Tomcat为了实现请求的快速响应,使用线程池来提高请求的处理能力。下面我们以HTTP非阻塞I/O为例对Tomcat线程池进行简要的分析。

在Tomcat中,通过AbstractEndpoint类提供底层的网络I/O的处理,若用户没有配置自定义公共线程池,则AbstractEndpoint通过createExecutor方法来创建Tomcat默认线程池。

核心部分代码如下:

其中,TaskQueue、ThreadPoolExecutor分别为Tomcat自定义任务队列、线程池实现。

Tomcat自定义线程池继承于java.util.concurrent.ThreadPoolExecutor,并新增了一些成员变量来更高效地统计已经提交但尚未完成的任务数量(submittedCount),包括已经在队列中的任务和已经交给工作线程但还未开始执行的任务。

Tomcat在自定义线程池ThreadPoolExecutor中重写了execute()方法,并实现对提交执行的任务进行submittedCount加一。Tomcat在自定义ThreadPoolExecutor中,当线程池抛出RejectedExecutionException异常后,会调用force()方法再次向TaskQueue中进行添加任务的尝试。如果添加失败,则submittedCount减一后,再抛出RejectedExecutionException。

在Tomcat中重新定义了一个阻塞队列TaskQueue,它继承于LinkedBlockingQueue。在Tomcat中,核心线程数默认值为10,最大线程数默认为200,为了避免线程到达核心线程数后后续任务放入队列等待,Tomcat通过自定义任务队列TaskQueue重写offer方法实现了核心线程池数达到配置数后线程的创建。

具体地,从线程池任务调度机制实现可知,当offer方法返回false时,线程池将尝试创建新新线程,从而实现任务的快速响应。TaskQueue核心实现代码如下:

Tomcat中通过自定义任务线程TaskThread实现对每个线程创建时间的记录;使用静态内部类WrappingRunnable对Runnable进行包装,用于对StopPooledThreadException异常类型的处理。

Executors常用方法有以下几个:

Executors类看起来功能比较强大、用起来还比较方便,但存在如下弊端

使用线程时,可以直接调用 ThreadPoolExecutor 的构造函数来创建线程池,并根据业务实际场景来设置corePoolSize、blockingQueue、RejectedExecuteHandler等参数。

使用局部线程池时,若任务执行完后没有执行shutdown()方法或有其他不当引用,极易造成系统资源耗尽。

在工程实践中,通常使用下述公式来计算核心线程数:

nThreads=(w+c)/c*n*u=(w/c+1)*n*u

其中,w为等待时间,c为计算时间,n为CPU核心数(通常可通过 Runtime.getRuntime().availableProcessors()方法获取),u为CPU目标利用率(取值区间为[0, 1]);在最大化CPU利用率的情况下,当处理的任务为计算密集型任务时,即等待时间w为0,此时核心线程数等于CPU核心数。

上述计算公式是理想情况下的建议核心线程数,而不同系统/应用在运行不同的任务时可能会有一定的差异,因此最佳线程数参数还需要根据任务的实际运行情况和压测表现进行微调。

为了更好地发现、分析和解决问题,建议在使用多线程时增加对异常的处理,异常处理通常有下述方案:

为了实现优雅停机的目标,我们应当先调用shutdown方法,调用这个方法也就意味着,这个线程池不会再接收任何新的任务,但是已经提交的任务还会继续执行。之后我们还应当调用awaitTermination方法,这个方法可以设定线程池在关闭之前的最大超时时间,如果在超时时间结束之前线程池能够正常关闭则会返回true,否则,超时会返回false。通常我们需要根据业务场景预估一个合理的超时时间,然后调用该方法。

如果awaitTermination方法返回false,但又希望尽可能在线程池关闭之后再做其他资源回收工作,可以考虑再调用一下shutdownNow方法,此时队列中所有尚未被处理的任务都会被丢弃,同时会设置线程池中每个线程的中断标志位。shutdownNow并不保证一定可以让正在运行的线程停止工作,除非提交给线程的任务能够正确响应中断。

ThreadLocal线程变量概述

ThreadLocal类提供了线程本地变量(thread-local variables),这些变量不同于普通的变量,访问线程本地变量的每个线程(通过其get或set方法)都有其自己的独立初始化的变量副本,因此ThreadLocal没有多线程竞争的问题,不需要单独进行加锁。

ThreadLocal的原理与实践

对于ThreadLocal而言,常用的方法有get/set/initialValue 3个方法。

众所周知,在java中SimpleDateFormat有线程安全问题,为了安全地使用SimpleDateFormat,除了1)创建SimpleDateFormat局部变量;和2)加同步锁 两种方案外,我们还可以使用3)ThreadLocal的方案:

Thread 内部维护了一个 ThreadLocal.ThreadLocalMap 实例(threadLocals),ThreadLocal 的操作都是围绕着 threadLocals 来操作的。

从JDK源码可见,ThreadLocalMap中的Entry是弱引用类型的,这就意味着如果这个ThreadLocal只被这个Entry引用,而没有被其他对象强引用时,就会在下一次GC的时候回收掉。

EagleEye(鹰眼)作为全链路监控系统在集团内部被广泛使用,traceId、rpcId、压测标等信息存储在EagleEye的ThreadLocal变量中,并在HSF/Dubbo服务调用间进行传递。EagleEye通过Filter将数据初始化到ThreadLocal中,部分相关代码如下:

在EagleEyeFilter中,通过EagleEyeRequestTracer.startTrace方法进行初始化,在前置入参转换后,通过startTrace重载方法将鹰眼上下文参数存入ThreadLocal中,相关代码如下:

EagleEyeFilter在finally代码块中,通过EagleEyeRequestTracer.endTrace方法结束调用链,通过clear方法将ThreadLocal中的数据进行清理,相关代码实现如下:

在某权益领取原有链路中,通过app打开一级页面后才能发起权益领取请求,请求经过淘系无线网关(Mtop)后到达服务端,服务端通过mtop sdk获取当前会话信息。

在XX项目中,对权益领取链路进行了升级改造,在一级页面请求时,通过服务端同时发起权益领取请求。具体地,服务端在处理一级页面请求时,同时通过调用hsf/dubbo接口来进行权益领取,因此在发起rpc调用时需要携带用户当前会话信息,在服务提供端将会话信息进行提取并注入到mtop上下文,从而才能通过mtop sdk获取到会话id等信息。某开发同学在实现时,因ThreadLocal使用不当造成下述问题:

【问题1:权益领取失败分析】

在权益领取服务中,该应用构建了一套高效和线程安全的依赖注入框架,基于该框架的业务逻辑模块通常抽象为xxxModule形式,Module间为网状依赖关系,框架会按依赖关系自动调用init方法(其中,被依赖的module 的init方法先执行)。

在应用中,权益领取接口的主入口为CommonXXApplyModule类,CommonXXApplyModule依赖XXSessionModule。当请求来临时,会按依赖关系依次调用init方法,因此XXSessionModule的init方法会优先执行;而开发同学在CommonXXApplyModule类中的init方法中通过调用recoverMtopContext()方法来期望恢复mtop上下文,因recoverMtopContext()方法的调用时机过晚,从而导致XXSessionModule模块获取不到正确的会话id等信息而导致权益领取失败。

【问题2:脏数据分析】

权益领取服务在处理请求时,若当前线程曾经处理过权益领取请求,因ThreadLocal变量值未被清理,此时XXSessionModule通过mtop SDK获取会话信息时得到的是前一次请求的会话信息,从而造成脏数据。

【解决方案】

在依赖注入框架入口处AbstractGate#visit(或在XXSessionModule中)通过recoverMtopContext方法注入mtop上下文信息,并在入口方法的finally代码块清理当前请求的threadlocal变量值。

若使用强引用类型,则threadlocal的引用链为:Thread -> ThreadLocal.ThreadLocalMap -> Entry[] -> Entry -> key(threadLocal对象)和value;在这种场景下,只要这个线程还在运行(如线程池场景),若不调用remove方法,则该对象及关联的所有强引用对象都不会被垃圾回收器回收。

若使用static关键字进行修饰,则一个线程仅对应一个线程变量;否则,threadlocal语义变为perThread-perInstance,容易引发内存泄漏,如下述示例:

在上述main方法第22行debug,可见线程的threadLocals变量中有3个threadlocal实例。在工程实践中,使用threadlocal时通常期望一个线程只有一个threadlocal实例,因此,若不使用static修饰,期望的语义发生了变化,同时易引起内存泄漏。

如果不执行清理操作,则可能会出现:

建议使用try...finally 进行清理。

我们在使用ThreadLocal时,通常期望的语义是perThread,若不使用static进行修饰,则语义变为perThread-perInstance;在线程池场景下,若不用static进行修饰,创建的线程相关实例可能会达到 M * N个(其中M为线程数,N为对应类的实例数),易造成内存泄漏(https://errorprone.info/bugpattern/ThreadLocalUsage)。

在应用中,谨慎使用ThreadLocal.withInitial(Supplier<? extends S> supplier)这个工厂方法创建ThreadLocal对象,一旦不同线程的ThreadLocal使用了同一个Supplier对象,那么隔离也就无从谈起了,如:

总结

在java工程实践中,线程池和线程变量被广泛使用,因线程池和线程变量的不当使用经常造成安全生产事故,因此,正确使用线程池和线程变量是每一位开发人员必须修炼的基本功。本文从线程池和线程变量的使用出发,简要介绍了线程池和线程变量的原理和使用实践,各开发人员可结合最佳实践和实际应用场景,正确地使用线程和线程变量,构建出稳定、高效的java应用服务。

关于线程池拒绝策略和线程池拒绝策略什么时候执行的介绍到此就结束了,不知道你从中找到你需要的信息了吗?如果你还想了解更多这方面的信息,记得收藏关注本站。

查看更多关于线程池拒绝策略的详细内容...

今日天气详情" target="_blank" onClick="allCount('首页_点击_实况天气_实况天气')"> 8 °

本篇文章给大家谈谈线程池拒绝策略,以及线程池拒绝策略什么时候执行对应的知识点,希望对各位有所帮助,不要忘了收藏本站!

内容导航:
  • 线程与线程池
  • 线程池工作原理
  • 线程池的四种创建方式及区别
  • 线程池的一些面试题
  • 线程池七大核心参数
  • 合理使用线程池以及线程变量

Q1:线程与线程池

1、线程的状态。5个。

2、实现线程的方法,及其区别。2种:Runnable、Thread(+2种:Callable、FutureTask)。

3、start()和run()的区别。

4、Thread.sleep()和Thread.yield()区别

yield,音标 /jild/。线程的礼让,该线程退回到就绪状态(然后所有的就绪的线程凭借优先级抢资源)。

sleep,线程的阻塞(当阻塞时间结束,线程转入就绪状态)。

5、wait和sleep的区别

1)wait是object的方法,sleep是thread的静态方法;

2)wait需要在synchronized范围内使用,否则抛错 Exception in thread "main" java.lang.IllegalMonitorStateException ,而sleep则不需要;

3)wait是对象监听器的线程的等待,当该对象wait时,当前线程进入等待,其notify方法是随机唤起一个(等待该对象监听器的)线程;sleep是当前线程的沉睡,该线程的对象锁还是持有的;

4)wait出让系统资源,进入线程池中等待;sleep不会出让锁。二者都会让出CPU。

5、用户线程(user Thread)和守护线程(daemon Thread)的区别。

1)守护线程的区别在于thread.setDaemon(true),设置了就是守护线程,且必须在start()之前设置。

2)守护线程依赖于用户线程,没有用户线程,守护线程不存在。即当用户线程运行完毕,此时不管守护线程是否运行或运行完毕,立即停止。

6、线程调用的两种方式:

1)直接使用start()方法(在主方法中显式迭代调用或者构造方法中,便于外部隐式调用);

2)使用Executor来调用(CachedThreadPool()或者FixedThreadPool())。

两种方法的区别是:Executor执行线程都是隐式的。而且在构造方法中调用start()方法对于多线程是不安全的,而Executor则不会。

7、停止一个运行中线程的方法:

1)interrupt方法;

2)使用退出标志;

3)stop,但不建议(J8废除,原因是可能导致数据不一致)。

关于stop方法,参考 https://blog.csdn.net/a158123/article/details/78776145 。

6、Executor调用线程的两种方式的区别:

newCachedThreadPool()会为每一个任务都分配线程;

newFixedThreadPool(long)会限定可使用的线程数量,在前面的任务执行完之后,会将空线程分配给其他的任务。

7、在并发时,一个任务不能依赖于另一个任务,因为任务的关闭顺序无法保证。解决:1.依赖于非任务对象(volatile变量)来解决。2.锁。

8、锁的方式:2种,synchronize和Lock。区别在于Lock更加细粒度,比如锁的尝试获取,锁的锁定时间。

9、线程池的状态:5个。

1.running

2.shutdown

3.stop

4.tidying(当workQueue为0时,进入该状态)

5.terminated

10、shutdown和stop的区别。

二者都有线程池停止之意,且都不接收新线程了。但shutdown会处理掉已接收和正在执行的线程,而stop会中断所有的已接收和正在执行的线程。

11、threadPoolExecutor的参数含义。

corePoolSize:核心线程数。即最小存活线程数。

maximunPoolSize:最大线程数。

keepAliveTime:当线程数大于核心线程是,线程的存活时间。

unit:keepAliveTime的单位。

workQueue:工作队列。在线程执行前,线程会放在此处。

threadFactory:线程创建工厂(有一个默认工厂)。

handler:拒绝策略。当多余的线程请求时,执行的策略。默认是拒绝策略。

ps:关于workQueue:当需要使用一个线程时,会先看核心线程有无空闲线程,若有,则直接使用,没有,则创建并放在队列中等待被使用;当线程用完时,也会放在队列中,等待一会,实在没人用且已达到核心数时,会消亡该线程。

newSingleThreadExecutor() 和 newFixedThreadPool() 都是用的LinkedBlockingQueue队列,而 newCachedThreadPool() 用的是SynchronousQueue队列。

在 newSingleThreadExecutor() 中,如果前一个线程出异常了,那么我就执行下一个线程,不会出现停止,而其他的线程池会导致停止。

在 newFixedThreadPool() 中keepAliveTime是0,在 newCachedThreadPool() 中keepAliveTime是60s。

12、线程的循环调用(如每隔5秒调用线程):

这是一个初始化后延迟1秒,每隔5秒执行任务(秒单位共享)。

这是一个初始化后延迟1毫秒,每隔5秒执行任务。默认单位为毫秒。

参考: Java多线程线程池(4)--线程池的五种状态 。

Q2:线程池工作原理

管理线程,当线程执行完当前任务,不会死掉而是 会从队列里面取

1.降低系统资源消耗。通过复用已存在的线程,降低线程创建和销毁造成的消耗;

2.提高响应速度。当有任务到达时,无需等待新线程的创建便能立即执行;

3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳定性,使用线程池可以进行对线程进行统一的分配、调优和监控。

本文主要是围绕 ThreadPoolExecutor(线程池框架的核心类)的构造方法参数 展开:

1.corePoolSize

线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

2.maximumPoolSize

额外最大线程数。上面说到任务数足够多,且使用的是有界队列,如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,首先从队列里面取,如果队列里面的消息执行完毕,等下一定时间,额外线程自动销毁。

3.keepAliveTime

线程空闲时的存活时间。默认情况下,可以理解成额外最大线程数没活干了,额外线程线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

4.unit

keepAliveTime参数的时间单位。

5.workQueue

任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。

一般来说,这里的BlockingQueue有以下三种选择:

* SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。

* LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量为Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。

* ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。

6.threadFactory

线程工厂,创建新线程时使用的线程工厂。

7.handler

任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:

*AbortPolicy :丢弃任务并抛出RejectedExecutionException异常,默认策略;

*CallerRunsPolicy :由调用execute方法的线程执行该任务;

*DiscardPolicy :丢弃任务,但是不抛出异常;

*DiscardOldestPolicy :丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。

* 当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

总结下上诉参数:假设corePoolSize为10 ,maximumPoolSize为10,线程空闲时的存活时间为60s,队列采用的是有界队列ArrayBlockingQueue 设置阈值200,使 用拒绝策略 , 当前2000个任务提交过来 流程如下图

参数案例描述:

         当前2000笔 任务进来,10个核心线程去处理,剩下的1990任务队列里面放200个。剩下的1790个任务。队列塞满会去创建10个额外线程和核心线程一起去 去执行剩下的1780个任务。 当还有剩下任务处理不了就会触发任务拒绝策略 。  

       当前220笔 任务进来,10个核心线程去处理,剩下的210任务队列里面放200个。剩下的10个任务。队列塞满会去创建10个额外线程 去执行队列放不下的任务。 当额外线程和核心线程处理完队列里面的队列。没有任务可执行时,额外线程会等待我们设置的keepAliveTime,还是没有任务的情况下,就会被回收了 。

以上是绝对理想的状况下。

由参数可知 核心线程 和额外线程值是相同的,额外线程被回收时间是0,采用的是无界队列。默认采用的拒绝策略为 AbortPolicy。分析得 核心线程和额外线程处理不过来得情况,会一直往队列里面放任务。

可能存在的问题:队列过大 导致内存溢出 OOM

当任务量足够大,超过队列。交由额外线程处理。就会创建过多线程。

可能存在问题:特殊场景下,线程过多可能会导致系统奔溃。cpu负载过高。

1.具体解决方案 根据业务系统而定:

         华瑞批量查证举例:定时任务CZJZRW001每隔2min 轮询一次 会从业务表verifycationTask 中 查询出待处理和处理中的状态的任务 根据表中的查证类型 分流到具体的 反欺诈异步查证 ,还款查证,充值查证,贷款查证 。 具体查证根据处理结果更新verifycationTask表查证状态。处理成功 或者失败的定时任务无法再次轮询。这样就不需要考虑以上场景。使用线程池的情况下核心线程,额外线程处理不过来且队列已满使用DiscardPolicy拒绝不抛异常策略 ,即可满足该业务场景。类结构如下图

2.思路

可以实现 RejectedExecutionHandler接口 自定义拒绝策略  将被拒绝的任务信息缓存到磁盘,等待线程池负载较低 从磁盘读取重新提交到任务里面去执行

Q3:线程池的四种创建方式及区别

核心线程数为0,非核心线程数为MAX_VALUE,

队列不存储值,总认为队列是满的,所以每次执行任务时都会创建非核心线程,非核心线程空闲了超过60秒(默认),就会自动回收。

2.newfixedThreadPool 创建定长的线程池

在达到长度之前,每提交一个任务都会创建一个线程,如果达到线程池最大数量,则提交到队列中,在空闲的时候也不会自动回收线程

核心线程数为参数传入,非核心线程数和核心线程数一样,

队列为无界队列,资源有限的时候容易引起OOM.

与newSingledThreadPool不同的是核心线程数不为1.

3.newSingledThreadPool 创建单一线程执行。

只有一个线程按顺序执行任务,如果这个线程出现异常结束,会有另一个线程取代并按顺序执行。

corepoolsize 核心线程数为1 ,非核心线程数为1 ,

队列为无界队列,

单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

4.newScheduedThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。如果延迟3秒执行或每隔3秒执行一次

核心线程数为 参数设定,非核心线程数为MAX_VALUE

定义了一个DelayedWorkQueue,它是一个有序队列,会通过每个任务按照距离下次执行时间间隔的大小来排序;

线程池执行逻辑说明:

判断核心线程数是否已满,核心线程数大小和corePoolSize参数有关,未满则创建线程执行任务

若核心线程池已满,判断队列是否满,队列是否满和workQueue参数有关,若未满则加入队列中

若队列已满,判断线程池是否已满,线程池是否已满和maximumPoolSize参数有关,若未满创建线程执行任务

若线程池已满,则采用拒绝策略处理无法执执行的任务,拒绝策略和handler参数有关

拒绝策略

拒绝策略 => 默认采用的是AbortPolicy拒绝策略,直接在程序中抛出RejectedExecutionException异常【因为是运行时异常,不强制catch】,这种处理方式不够优雅。处理拒绝策略有以下几种比较推荐:

在程序中捕获RejectedExecutionException异常,在捕获异常中对任务进行处理。针对默认拒绝策略

使用CallerRunsPolicy拒绝策略,该策略会将任务交给调用execute的线程执行【一般为主线程】,此时主线程将在一段时间内不能提交任何任务,从而使工作线程处理正在执行的任务。此时提交的线程将被保存在TCP队列中,TCP队列满将会影响客户端,这是一种平缓的性能降低

自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可

如果任务不是特别重要,使用DiscardPolicy和DiscardOldestPolicy拒绝策略将任务丢弃也是可以

public class ThreadTest {

//ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

//

//scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

//public void run() {

//System.out.println("delay 1 seconds, and excute every 3 seconds");

//

//}

//

//}, 1, 3, TimeUnit.SECONDS);

}

Q4:线程池的一些面试题

1,为什么要用线程池,优势

(1)降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2) 提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行。

(3)  提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

1.1常用方式

那java中是怎样实现的线程池呢?是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个接口或类,它们都是JUC包下的。 java.util.concurrent.Executors类是Executor的辅助类,类似于java中操作数组的辅助类java.util.Arrays,以及操作集合的java.util.Collections类

1.2:Executors类中的主要三个方法

线程安全的队列:staticQueue queue = new ConcurrentLinkedQueue<String>();

(1) 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中的等待,它创建的线程池corePoolSize和maximnumPoolSize是相等的,它使用的是LinkedBlockingQueue;

源码如下:

  public static ExecutorService newFixedThreadPool(int nThreads) {

        return new ThreadPoolExecutor(nThreads, nThreads,

                                      0L, TimeUnit.MILLISECONDS,

                                      new LinkedBlockingQueue<Runnable>());

    }

(2)Executors#newSingleThreadExecutor

    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行,它将corePoolSize和maximnumPoolSize都设置为1,它也使用的是LinkedBlockingQueue;

源码:

public static ExecutorService newSingleThreadExecutor() {

        return new FinalizableDelegatedExecutorService

            (new ThreadPoolExecutor(1, 1,

                                    0L, TimeUnit.MILLISECONDS,

                                    new LinkedBlockingQueue<Runnable>()));

    }

(3)Executors#newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。,它将corePoolSize设置为0,将maximnumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,当前线程空闲超过60秒,就销毁线程;

源码:

public static ExecutorService newCachedThreadPool() {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,

                                      60L, TimeUnit.SECONDS,

                                      new SynchronousQueue<Runnable>());

    }

2,线程池的重要参数:

源码:

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

参数:

corePoolSize

    线程池中的常驻核心线程数,在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。

maximumPoolSize

    线程池能够容纳同时执行的最大线程数,此值必须大于等于1。

keepAliveTime

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

unit

    keepAliveTime的单位。

workQueue

    任务队列,被提交但尚未被执行的任务。

threadFactory

    表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。

handler

    拒绝策略,表示当队列满了,再也塞不下新任务了,同时,工作线程大于等于线程池的最大线程数,无法继续为新任务服务,这时候我们就需要拒绝策略机制合理的处理这个问题,默认会抛异常, 那拒绝策略有哪些呢,我们继续往下看。

JDK内置的接口:RejectedExcutionHandle

AbortPolicy(默认)

    直接抛出java.util.concurrent.RejectedExecutionException异常阻止系统正常运行,这种方式显然是不友好的。

CallerRunsPolicy

    "调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

DiscardOldestPolicy

    抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

DiscardPolicy

    直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种解决方案。

    具体选择哪一种的拒绝策略,也是看自己的系统需求了;

3,底层工作原理

(1).在创建了线程池后,等待提交过来的任务请求

 (2).当调用execute()方法添加一个请求任务时,线程池会做如下判断

            2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务

            2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列

            2.3 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务

            2.4 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行

    (3). 当一个线程完成任务时,它会从队列中取下一个任务来执行

    (4). 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断

            4.1 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉

            4.2 所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小

创建线程池时,配置多少线程数是合理的:

(1)CPU密集型:CPU核数+1个线程的线程池(CPU密集任务只有在真正的多核CPU上才可能得到加速)

(2)IO密集型:O密集型时,大部分线程都阻塞,故需要多配置线程数,CPU核数/1-阻塞系数 阻塞系数在0.8至0.9之间。例如4核,取个乐观值0.9,可达到40个线程左右

------------------------------------------------------------------------

--------------------------------

阿里巴巴开发手册上:出自于生产时间来说:

1,线程资源必须通过线程池提供,不能够在应用中自行创建线程;

2,线程池不允许使用Executors去创建,而是使用ThreadPoolExecutor的方式,

这样可以明确线程池的规则,规避资源耗尽的风险;

---------------------------

SpringBoot 自定义线程池:

1,application.yml配置:

task:

pool:

corePoolSize:5#设置核心线程数

maxPoolSize:20#设置最大线程数

keepAliveSeconds:300#设置线程活跃时间(秒)

queueCapacity:50#设置队列容量

2,线程池配置属性类:

importorg.springframework.boot.context.properties.ConfigurationProperties;

/**

* 线程池配置属性类

*/

@ConfigurationProperties(prefix ="task.pool")

publicclassTaskThreadPoolConfig{

privateintcorePoolSize;

privateintmaxPoolSize;

privateintkeepAliveSeconds;

privateintqueueCapacity;

    ...getter and setter methods...

}

3,启动类上加上异步支持:

@EnableAsync

@EnableConfigurationProperties({TaskThreadPoolConfig.class} )// 开启配置属性支持

4,自定义线程池:

/**

* 创建线程池配置类

*/

@Configuration

public class TaskExecutePool {

    @Autowired

    private TaskThreadPoolConfig config;

    /**

   * 1.这种形式的线程池配置是需要在使用的方法上面@Async("taskExecutor"),

   * 2.如果在使用的方法上面不加该注解那么spring就会使用默认的线程池

   * 3.所以如果加@Async注解但是不指定使用的线程池,又想自己定义线程池那么就可以重写spring默认的线程池

   * 4.所以第二个方法就是重写默认线程池

   * 注意:完全可以把线程池的参数写到配置文件中

   */

    @Bean

    public Executor taskExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //核心线程池大小

        executor.setCorePoolSize(config.getCorePoolSize());

        //最大线程数

        executor.setMaxPoolSize(config.getMaxPoolSize());

        //队列容量

        executor.setQueueCapacity(config.getQueueCapacity());

        //活跃时间

        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());

        //线程名字前缀

        executor.setThreadNamePrefix("TaskExecutePool-");

        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务

        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

       // 等待所有任务结束后再关闭线程池

        executor.setWaitForTasksToCompleteOnShutdown(true);

        executor.initialize();

        return executor;

    }

}

测试:

import io.swagger.annotations.Api;

import io.swagger.annotations.ApiOperation;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;

/**

* @author qijx

*/

@Api(description = "测试控制类11111")

@RestController

@RequestMapping("/threadPoolController1")

public class ThreadPoolController1 {

        @Autowired

        private ThreadPoolService1 threadPoolService;

        @ApiOperation(value = "测试方法")

        @ResponseBody

        @RequestMapping(value = "/test",method = RequestMethod.GET)

        public String threadPoolTest() {

            threadPoolService.executeAsync();

            return "hello word!";

        }

}

第二种方法:重写springboot线程池:

**

* 原生(Spring)异步任务线程池装配类,实现AsyncConfigurer重写他的两个方法,这样在使用默认的

*  线程池的时候就会使用自己重写的

*/

@Slf4j

@Configuration

public class NativeAsyncTaskExecutePool implements AsyncConfigurer{

    //注入配置类

    @Autowired

    TaskThreadPoolConfig config;

    @Override

    public Executor getAsyncExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //核心线程池大小

        executor.setCorePoolSize(config.getCorePoolSize());

        //最大线程数

        executor.setMaxPoolSize(config.getMaxPoolSize());

        //队列容量

        executor.setQueueCapacity(config.getQueueCapacity());

        //活跃时间

        executor.setKeepAliveSeconds(config.getKeepAliveSeconds());

        //线程名字前缀

        executor.setThreadNamePrefix("NativeAsyncTaskExecutePool-");

        // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务

        // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 等待所有任务结束后再关闭线程池

        executor.setWaitForTasksToCompleteOnShutdown(true);

        executor.initialize();

        return executor;

    }

    /**

   *  异步任务中异常处理

   * @return

   */

    @Override

    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {

        return new AsyncUncaughtExceptionHandler() {

            @Override

            public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {

                log.error("=========================="+arg0.getMessage()+"=======================", arg0);

                log.error("exception method:"+arg1.getName());

            }

        };

    }

}

测试:

/**

* @author qijx

*/

@Service

public class ThreadPoolService2 {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPoolService2.class);

    /**

   * @Async该注解不需要在指定任何bean

   */

    @Async

    public void executeAsync() {

        logger.info("start executeAsync");

        try {

            System.out.println("当前运行的线程名称:" + Thread.currentThread().getName());

            Thread.sleep(1000);

        } catch (Exception e) {

            e.printStackTrace();

        }

        logger.info("end executeAsync");

    }

}

------------------------------------

Q5:线程池七大核心参数

线程池七大核心参数如下所示:


一、corePoolSize 线程池核心线程大小


线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。


二、maximumPoolSize 线程池最大线程数量


当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列(后面会介绍)中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。


三、keepAliveTime 空闲线程存活时间


一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。


四、unit 空闲线程存活时间单位


空闲线程存活时间单位是keepAliveTime的计量单位。


五、workQueue 工作队列


新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。


六、threadFactory 线程工厂


创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。


七、handler 拒绝策略


当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的。


线程池的优势


1、线程和任务分离,提升线程重用性;


2、控制线程并发数量,降低服务器压力,统一管理所有线程;


3、提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间。

Q6:合理使用线程池以及线程变量

背景

随着计算技术的不断发展,3纳米制程芯片已进入试产阶段,摩尔定律在现有工艺下逐渐面临巨大的物理瓶颈,通过多核处理器技术来提升服务器的性能成为提升算力的主要方向。

在服务器领域,基于java构建的后端服务器占据着领先地位,因此,掌握java并发编程技术,充分利用CPU的并发处理能力是一个开发人员必修的基本功,本文结合线程池源码和实践,简要介绍了线程池和线程变量的使用。

线程池概述

线程池是一种“池化”的线程使用模式,通过创建一定数量的线程,让这些线程处于就绪状态来提高系统响应速度,在线程使用完成后归还到线程池来达到重复利用的目标,从而降低系统资源的消耗。

总体来说,线程池有如下的优势:

线程池的使用

在java中,线程池的实现类是ThreadPoolExecutor,构造函数如下:

可以通过 new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,handler)来创建一个线程池。

在构造函数中,corePoolSize为线程池核心线程数。默认情况下,核心线程会一直存活,但是当将allowCoreThreadTimeout设置为true时,核心线程超时也会回收。

在构造函数中,maximumPoolSize为线程池所能容纳的最大线程数。

在构造函数中,keepAliveTime表示线程闲置超时时长。如果线程闲置时间超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。

在构造函数中,timeUnit表示线程闲置超时时长的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

在构造函数中,blockingQueue表示任务队列,线程池任务队列的常用实现类有:

在构造函数中,threadFactory表示线程工厂。用于指定为线程池创建新线程的方式,threadFactory可以设置线程名称、线程组、优先级等参数。如通过Google工具包可以设置线程池里的线程名:

在构造函数中,rejectedExecutionHandler表示拒绝策略。当达到最大线程数且队列任务已满时需要执行的拒绝策略,常见的拒绝策略如下:

ThreadPoolExecutor线程池有如下几种状态:

线程池提交一个任务时任务调度的主要步骤如下:

核心代码如下:

Tomcat 的整体架构包含连接器和容器两大部分,其中连接器负责与外部通信,容器负责内部逻辑处理。在连接器中:

Tomcat为了实现请求的快速响应,使用线程池来提高请求的处理能力。下面我们以HTTP非阻塞I/O为例对Tomcat线程池进行简要的分析。

在Tomcat中,通过AbstractEndpoint类提供底层的网络I/O的处理,若用户没有配置自定义公共线程池,则AbstractEndpoint通过createExecutor方法来创建Tomcat默认线程池。

核心部分代码如下:

其中,TaskQueue、ThreadPoolExecutor分别为Tomcat自定义任务队列、线程池实现。

Tomcat自定义线程池继承于java.util.concurrent.ThreadPoolExecutor,并新增了一些成员变量来更高效地统计已经提交但尚未完成的任务数量(submittedCount),包括已经在队列中的任务和已经交给工作线程但还未开始执行的任务。

Tomcat在自定义线程池ThreadPoolExecutor中重写了execute()方法,并实现对提交执行的任务进行submittedCount加一。Tomcat在自定义ThreadPoolExecutor中,当线程池抛出RejectedExecutionException异常后,会调用force()方法再次向TaskQueue中进行添加任务的尝试。如果添加失败,则submittedCount减一后,再抛出RejectedExecutionException。

在Tomcat中重新定义了一个阻塞队列TaskQueue,它继承于LinkedBlockingQueue。在Tomcat中,核心线程数默认值为10,最大线程数默认为200,为了避免线程到达核心线程数后后续任务放入队列等待,Tomcat通过自定义任务队列TaskQueue重写offer方法实现了核心线程池数达到配置数后线程的创建。

具体地,从线程池任务调度机制实现可知,当offer方法返回false时,线程池将尝试创建新新线程,从而实现任务的快速响应。TaskQueue核心实现代码如下:

Tomcat中通过自定义任务线程TaskThread实现对每个线程创建时间的记录;使用静态内部类WrappingRunnable对Runnable进行包装,用于对StopPooledThreadException异常类型的处理。

Executors常用方法有以下几个:

Executors类看起来功能比较强大、用起来还比较方便,但存在如下弊端

使用线程时,可以直接调用 ThreadPoolExecutor 的构造函数来创建线程池,并根据业务实际场景来设置corePoolSize、blockingQueue、RejectedExecuteHandler等参数。

使用局部线程池时,若任务执行完后没有执行shutdown()方法或有其他不当引用,极易造成系统资源耗尽。

在工程实践中,通常使用下述公式来计算核心线程数:

nThreads=(w+c)/c*n*u=(w/c+1)*n*u

其中,w为等待时间,c为计算时间,n为CPU核心数(通常可通过 Runtime.getRuntime().availableProcessors()方法获取),u为CPU目标利用率(取值区间为[0, 1]);在最大化CPU利用率的情况下,当处理的任务为计算密集型任务时,即等待时间w为0,此时核心线程数等于CPU核心数。

上述计算公式是理想情况下的建议核心线程数,而不同系统/应用在运行不同的任务时可能会有一定的差异,因此最佳线程数参数还需要根据任务的实际运行情况和压测表现进行微调。

为了更好地发现、分析和解决问题,建议在使用多线程时增加对异常的处理,异常处理通常有下述方案:

为了实现优雅停机的目标,我们应当先调用shutdown方法,调用这个方法也就意味着,这个线程池不会再接收任何新的任务,但是已经提交的任务还会继续执行。之后我们还应当调用awaitTermination方法,这个方法可以设定线程池在关闭之前的最大超时时间,如果在超时时间结束之前线程池能够正常关闭则会返回true,否则,超时会返回false。通常我们需要根据业务场景预估一个合理的超时时间,然后调用该方法。

如果awaitTermination方法返回false,但又希望尽可能在线程池关闭之后再做其他资源回收工作,可以考虑再调用一下shutdownNow方法,此时队列中所有尚未被处理的任务都会被丢弃,同时会设置线程池中每个线程的中断标志位。shutdownNow并不保证一定可以让正在运行的线程停止工作,除非提交给线程的任务能够正确响应中断。

ThreadLocal线程变量概述

ThreadLocal类提供了线程本地变量(thread-local variables),这些变量不同于普通的变量,访问线程本地变量的每个线程(通过其get或set方法)都有其自己的独立初始化的变量副本,因此ThreadLocal没有多线程竞争的问题,不需要单独进行加锁。

ThreadLocal的原理与实践

对于ThreadLocal而言,常用的方法有get/set/initialValue 3个方法。

众所周知,在java中SimpleDateFormat有线程安全问题,为了安全地使用SimpleDateFormat,除了1)创建SimpleDateFormat局部变量;和2)加同步锁 两种方案外,我们还可以使用3)ThreadLocal的方案:

Thread 内部维护了一个 ThreadLocal.ThreadLocalMap 实例(threadLocals),ThreadLocal 的操作都是围绕着 threadLocals 来操作的。

从JDK源码可见,ThreadLocalMap中的Entry是弱引用类型的,这就意味着如果这个ThreadLocal只被这个Entry引用,而没有被其他对象强引用时,就会在下一次GC的时候回收掉。

EagleEye(鹰眼)作为全链路监控系统在集团内部被广泛使用,traceId、rpcId、压测标等信息存储在EagleEye的ThreadLocal变量中,并在HSF/Dubbo服务调用间进行传递。EagleEye通过Filter将数据初始化到ThreadLocal中,部分相关代码如下:

在EagleEyeFilter中,通过EagleEyeRequestTracer.startTrace方法进行初始化,在前置入参转换后,通过startTrace重载方法将鹰眼上下文参数存入ThreadLocal中,相关代码如下:

EagleEyeFilter在finally代码块中,通过EagleEyeRequestTracer.endTrace方法结束调用链,通过clear方法将ThreadLocal中的数据进行清理,相关代码实现如下:

在某权益领取原有链路中,通过app打开一级页面后才能发起权益领取请求,请求经过淘系无线网关(Mtop)后到达服务端,服务端通过mtop sdk获取当前会话信息。

在XX项目中,对权益领取链路进行了升级改造,在一级页面请求时,通过服务端同时发起权益领取请求。具体地,服务端在处理一级页面请求时,同时通过调用hsf/dubbo接口来进行权益领取,因此在发起rpc调用时需要携带用户当前会话信息,在服务提供端将会话信息进行提取并注入到mtop上下文,从而才能通过mtop sdk获取到会话id等信息。某开发同学在实现时,因ThreadLocal使用不当造成下述问题:

【问题1:权益领取失败分析】

在权益领取服务中,该应用构建了一套高效和线程安全的依赖注入框架,基于该框架的业务逻辑模块通常抽象为xxxModule形式,Module间为网状依赖关系,框架会按依赖关系自动调用init方法(其中,被依赖的module 的init方法先执行)。

在应用中,权益领取接口的主入口为CommonXXApplyModule类,CommonXXApplyModule依赖XXSessionModule。当请求来临时,会按依赖关系依次调用init方法,因此XXSessionModule的init方法会优先执行;而开发同学在CommonXXApplyModule类中的init方法中通过调用recoverMtopContext()方法来期望恢复mtop上下文,因recoverMtopContext()方法的调用时机过晚,从而导致XXSessionModule模块获取不到正确的会话id等信息而导致权益领取失败。

【问题2:脏数据分析】

权益领取服务在处理请求时,若当前线程曾经处理过权益领取请求,因ThreadLocal变量值未被清理,此时XXSessionModule通过mtop SDK获取会话信息时得到的是前一次请求的会话信息,从而造成脏数据。

【解决方案】

在依赖注入框架入口处AbstractGate#visit(或在XXSessionModule中)通过recoverMtopContext方法注入mtop上下文信息,并在入口方法的finally代码块清理当前请求的threadlocal变量值。

若使用强引用类型,则threadlocal的引用链为:Thread -> ThreadLocal.ThreadLocalMap -> Entry[] -> Entry -> key(threadLocal对象)和value;在这种场景下,只要这个线程还在运行(如线程池场景),若不调用remove方法,则该对象及关联的所有强引用对象都不会被垃圾回收器回收。

若使用static关键字进行修饰,则一个线程仅对应一个线程变量;否则,threadlocal语义变为perThread-perInstance,容易引发内存泄漏,如下述示例:

在上述main方法第22行debug,可见线程的threadLocals变量中有3个threadlocal实例。在工程实践中,使用threadlocal时通常期望一个线程只有一个threadlocal实例,因此,若不使用static修饰,期望的语义发生了变化,同时易引起内存泄漏。

如果不执行清理操作,则可能会出现:

建议使用try...finally 进行清理。

我们在使用ThreadLocal时,通常期望的语义是perThread,若不使用static进行修饰,则语义变为perThread-perInstance;在线程池场景下,若不用static进行修饰,创建的线程相关实例可能会达到 M * N个(其中M为线程数,N为对应类的实例数),易造成内存泄漏(https://errorprone.info/bugpattern/ThreadLocalUsage)。

在应用中,谨慎使用ThreadLocal.withInitial(Supplier<? extends S> supplier)这个工厂方法创建ThreadLocal对象,一旦不同线程的ThreadLocal使用了同一个Supplier对象,那么隔离也就无从谈起了,如:

总结

在java工程实践中,线程池和线程变量被广泛使用,因线程池和线程变量的不当使用经常造成安全生产事故,因此,正确使用线程池和线程变量是每一位开发人员必须修炼的基本功。本文从线程池和线程变量的使用出发,简要介绍了线程池和线程变量的原理和使用实践,各开发人员可结合最佳实践和实际应用场景,正确地使用线程和线程变量,构建出稳定、高效的java应用服务。

关于线程池拒绝策略和线程池拒绝策略什么时候执行的介绍到此就结束了,不知道你从中找到你需要的信息了吗?如果你还想了解更多这方面的信息,记得收藏关注本站。

查看更多关于线程池拒绝策略的详细内容...

今日天气详情" target="_blank">阴
空气质量94优
未来2小时内无雨~

硬核技术宅侦探和他的007黑猫——《迷雾侦探》评测

    2015年,硬核技术 到访美国的中国游客总数达300万人次,同比增长16%。

    宅侦探和而政党在执政或选择竞选策略时也更容易受到与自己政治立场相近的智库的影响。英国特定的政治制度是造成智库依附政党的主要原因,黑猫迷保守主义和激进主义相互竞争、黑猫迷相互依存的局面为智库的意识形态化提供了土壤,与政党建立有效的联系渠道成为智库实现影响力的理性选择。

    硬核技术宅侦探和他的007黑猫——《迷雾侦探》评测

    2、雾侦探评英国智库成为促进政治范式转变的有力推手。英国智库与利益集团、硬核技术政见社团、硬核技术政党和决策精英一起,共同推动英国政府实现了三阶段的决策范式转变,即从1960年代的福利主义导向转变为1980年代的新自由主义导向。像ASI、宅侦探和经济事务研究所(IEA)和政策研究中心(CPS)与保守党关系密切,主张回归经济自由主义和“小政府”,为撒切尔政府的执政提供了帮助。3、黑猫迷“旋转门”机制成为保障英国智库影响力的直接因素。雾侦探评“旋转门”是欧美思想库比较重要的现象。

    由于两党或多党执政的特点,硬核技术每逢换届,卸任的官员很多会进入智库,很多智库的研究者会进入政府担任职务,从而完成研究者与执政者的角色转换。“旋转门”机制不仅构建了智库的人际关系网络,宅侦探和其次搭建了知识与权力的桥梁,宅侦探和成为不同利益群体向最高决策者传递利益诉求的重要纽带,使智库的影响力渗透到政策制定的方方面面。机构数大幅增加,黑猫迷床位数增幅不大,说明小微机构数量增加,侧重于几十张床位的日间照料,以及150-300张左右的小型机构。

    日间照料床位数278.7万张,雾侦探评占所有床位数量的1/3,雾侦探评日间照料这个数字在2011年之前是没有的,2012年开始出现日间照料这种形态,当年只有床位数19.8万张,到2015年仅仅三年时间比其翻了10倍。另外,硬核技术居家养老成为养老服务供给的基础力量,机构养老作为补充。《北京市居家养老服务条例》颁布,宅侦探和明确了居家养老服务的概念和内容,以及赡养人及相关机构应承担的义务与职责。随着政策的不断出台与市场对养老需求的不断摸索,黑猫迷政策内容与市场实践的重点不断从机构型养老向居家和社区养老转移,黑猫迷居家养老逐渐成为政策制定的新主角以及市场角逐的新战场,发挥其为老人提供服务的重要市场角色。

    与此同时,机构养老在养老服务体系中的地位与职能,也由最开始作为养老产业迅速发展之后养老服务体系的主要“支撑者”,逐步回归到居家与社区养老的“补充者”的位置。2015年《北京市居家养老服务条例》 颁布,北京市已有慈爱嘉、青松康护等35家机构提供政府购买服务,越来越多的居家养老服务中心开始涌现,并结合多种多样的养老产品,提供优质的居家养老服务,使得”9073”中90%的人群真正能够原居安老。

    硬核技术宅侦探和他的007黑猫——《迷雾侦探》评测

    同时,医养结合发展迅速,健康的中国提出,引发10万亿规模。截至2016年2月末,全国已经有4609家医院与养老机构签订了服务协议。2015年10月底,国家中医药管理局与全国老龄委签署了《关于推进中医药健康养老服务发展的合作协议》。这一政策明确了中医在医养结合过程中的独特地位与作用,特别提出了在中医药方面,如何与健康养老产业,特别是在养生保健方面进行结合,并将养老服务列入中医药发展的“十三五”规划中。

    最后,跨区域统一布局的模式出现,京津冀养老圈,长三角、珠三角等,合力破解跨区域老年福利和养老服务方面的身份和户籍障碍,在社会保障、养老保险、救助补贴等方面做好政策制度对接。在京津冀之后,长三角、珠三角等东部经济发达地区必然会借鉴京津冀发展模式,结合自身地域特点进行健康养老产业协同布局。03细分市场快速完善更加均衡合理的产业结构2015年以前的养老市场几乎处在混沌的状态,大量的探索和尝试集中在产业链下游的养老服务端。2015年初的一个数字,单纯做养老服务是不赚钱的,40%的机构是不盈利的,只有9%是盈利的,其中78%的机构盈利率是5%,非常微利。

    其实老年需求是多元化、多层次的,从现有老龄产业的终端产品(产品、服务)看,不仅种类单一,甚至存在空白,不仅行业界限不清晰,行业上下游之间没有符合市场规律的构建提要:北京大学互联网金融研究中心最近开发了三个指数,帮助把握互联网金融的发展状况和关注水平。

    硬核技术宅侦探和他的007黑猫——《迷雾侦探》评测

    第一个是北京大学互联网金融情绪指数。自2013年年初起,对互联网金融的关注度一直在上升,2016年年初才有所回落。

    但情感指数却一直在大幅震荡,特别是2015年下半年P2P平台问题频发,之后情感指数一直处于负值区间。第二个是北京大学互联网金融发展指数。从2014年年初开始,发展水平大概是平均每年翻一番。地级市数据表明,离杭州的距离越远,发展水平越低。年轻人特别是九零后是互联网金融发展的主要推动力量。第三个是北京大学数字普惠金融指数。

    2015年,数字普惠金融水平已经是2011年的5.5倍,省级、市级和县级数据还表明,各地水平的差异正在快速缩小,充分反映了以互联网技术推动普惠金融发展的优势。在过去的一年里,北京大学互联网金融研究中心开发了三个关于中国互联网金融的指数:北京大学互联网金融情绪指数、北京大学互联网金融发展指数和北京大学数字普惠金融指数。

    这几个指数可以帮助把握中国互联网金融发展的状况以及公众对于互联网金融的情绪。所有这些数据都会持续地更新、发布到中心的网站上(http://iif.pku.edu.cn)。

    一、北京大学互联网金融情绪指数北京大学互联网情绪指数是由王靖一和窦笑添开发的,主要是利用大数据的方法,从1400万条新闻中提炼、测算出来两个分指数,一是关注度指数,二是正负情感指数。图1中,蓝色这条线是关注度指数,这是一个月度数据。

    总体看来,过去三年半,对互联网金融的关注度一直在上升,特别是在两个阶段:第一个阶段,从2013年6月余额宝上线到2014年初“互联网金融”被写入政府工作报告。第二个阶段,从2015年P2P平台出现风险到年底监管草案公布。2016年年初以来,关注度有所下降,但仍然远高于几年前的水平。红色这条线是情感指数,可以看到过去这段时间,虽然关注度在上升,但情感一直非常复杂, 在正负之间大幅波动。

    2015年第四季度开始从高值快速回落,2016年第二季度初达到了最低点。不过最近又有所恢复,几乎回到了历史平均水平

    作者:戴斌,中国旅游研究院院长。该文系作者于2016年5月28日在苏黎世全球百货公司峰会上的主题演讲。

    主席先生、各位商界领袖、媒体朋友:上午好。三年前,我在芝加哥参加中美省州旅游局长会,有两个画面至今难忘。

    一是当地的主流媒体报道“中国游客来了”,所配的图片主要是中国人拎着大包小包的购物袋 从梅西百货和大大小小的精品店出来,笑意盈盈地走在繁华的街道上。另一个是陪同我的当地华侨说,没想到现在同胞们这么有钱,更没想到你们这么愿意消费,似 乎全世界都在发自内心地欢迎中国人的到来。与此同时,我也注意到有媒体渲染中国游客似乎不解风情,就知道“买买买”,而且就知道挑贵的买,还不分场合地大跳广场舞。两相比较,究竟哪一种才是最真实的中国游客画像呢?今天,包括出境在内的旅游消费已经确确实实地进入了国民大众的日常生活,成为人民生活水平提升的重要组成部分。

    中国人自古以来就有“读万卷书,行万里路”的传统。然而,受经济发展水平、休闲理念和交通工具等多种因素的局限,能够像徐霞客和马可·波罗那样周游世界的人还是很少的,大多数老百姓终其一生都是生活在某个熟人熟地的空间里。

    至少就当代旅游发展历史的角度而言,1999年是一个标志性的年份。当年国庆节史无前例地放了七天长假,即世人所熟悉的“黄金周”。

    从那时起,越来越多的国民参与到旅游休闲中来,不仅为国家旅游经济运行构筑坚实的市场基础,也以其持续高速增长的出境旅游人次有力支撑了世界旅游市场的繁荣与发展。2015年,国内旅游市场达到创记录的40亿人次,与1.34亿的入境旅游人次共同创造了约 6400亿美元的旅游收入。

  • 俄媒称现代航母依旧是“海上霸主”? 俄媒称现代航母依旧是“海上霸主”?
  • 39亿彩票巨奖得主怒告亲儿 只因收益缩水6千万 39亿彩票巨奖得主怒告亲儿 只因收益缩水6千万
  • 震撼大片来袭!121岁的北大正青春!四代人同唱这首歌 震撼大片来袭!121岁的北大正青春!四代人同唱这首歌
  • 超过《红海行动》,《复联4》跻身中国内地票房总榜前三甲 超过《红海行动》,《复联4》跻身中国内地票房总榜前三甲
  • 专访奔驰女车主:压力大时想自杀 欠款争议相信司法裁定 专访奔驰女车主:压力大时想自杀 欠款争议相信司法裁定
  • 布洛芬被指使用不慎可能致死,孩子发烧还能吃退烧药吗? 布洛芬被指使用不慎可能致死,孩子发烧还能吃退烧药吗?
  • 杭州动漫节上这张小纸条火了 有人留言:希望你们 杭州动漫节上这张小纸条火了 有人留言:希望你们"失业"
  • 德国警方摧毁全球第二大暗网交易平台 德国警方摧毁全球第二大暗网交易平台
  • 新疆大学唱响《歌唱祖国》 新疆大学唱响《歌唱祖国》
  • 西南联大与清华校长梅贻琦日记:通才教育和学术自由 西南联大与清华校长梅贻琦日记:通才教育和学术自由

城市天气预报

  • 热门
  • ABCD
  • EFGH
  • JKLM
  • NPQR
  • STWX
  • YZ

国际城市天气预报

  • 亚洲
  • 欧洲
  • 美洲
  • 大洋洲
  • 非洲

合作伙伴

气温排行榜

  • 高温
  • 低温
排名 城市 今天气温
1 “星爵”与施瓦辛格女儿办婚前派对,准岳父出席笑逐颜开临沂商城奋斗模式火力全开 23~26 °
2 女主播直播暴雨 浑身湿透老赖珍藏两个手机靓号 法院拍卖六十多万 23~25 °
3 第一个整族脱贫的少数民族"乐清失联男孩"母亲报假警一审被判1年3个月 23~25 °
4 三星S10 5G超越华为P30 Pro夺得DxOMark榜首智慧栽实控人突遭拘留 23~25 °
5 第五套人民币发布 5毛变银色戴森V11干掉扫地机器人和保姆 23~25 °
6 青春喜剧《大大哒》定档5.24 元气胖妞蜕变女神科尔批技犯累积禁赛制度盼改革:季后赛走更远反不利 23~25 °
7 只怪咱没住上这5个宿舍财运不知不觉到来的3生肖,财源滚滚,大获钱财! 20~25 °
8 怀孕期间丈夫被外派到新疆,我让他辞职回家陪我有错吗?醉酒托词暴露黄心颖主动真相 20~25 °
9 江苏盐城化工厂爆炸厂区被完全摧毁 核心区现巨坑名模吕燕发律师函指控影儿集团抄袭 反被扒也曾“抄大牌” 17~25 °
10 第6期|张海亮:未来可期 交付不是天际的瓶颈任天堂Switch累计销量3400万台 12~25 °
查看更多>
1 艺术怎样改变黄土高坡小村庄俄总参谋长:美欲借其全球反导系统剥夺俄反击能力 -24~-12 °
2 高速出现这4项都是你责任[专访]康劲:照片年卖百万 -24~-9 °
3 AI体测、智能试衣、智慧客厅,南都记者揭秘“中国品牌日”看点温州失联男孩母亲一审被判处有期徒刑1年3个月 -23~-12 °
4 复仇者联盟成员人气排行榜发布哪四类女人易遭到老公的背叛 -23~-8 °
5 报告总裁,你家萌妻要出墙2019款“路虎卫士”谍报曝光,新车或于9月正式上市! -20~-8 °
6 包贝尔带女儿走红毯 饺子涂红唇戴皇冠似公主回顾:平行志愿这样填不浪费分 -20~-8 °
7 专访|洛凡:对喜欢自己的人负责旧金山移民局档案,民国初期华裔护照 -20~-5 °
8 2018-10-21 期加油向未来 第3季丁俊晖挑战花式打法秀呆撒贝宁知识科普马斯克:特斯拉下一代跑车续航超1000公里 -19~-10 °
9 罗永浩宣布小野电子烟上市:前员工开发、锤子设计CBA-16连胜!广东现王者风范 新疆0-2被逼上绝境 -18~-12 °
10 袁立晒“一家三口”合照疑似升级当妈?她只回复了3个字…王健林宣布万达回归足坛 塔利斯卡至少伤停10天缺战国安 -18~-10 °
查看更多>

空气质量排行榜

  • 最优
  • 最差
排名 城市 今天空气
1 国庆北京首贼大兴落网 身藏6部手机数张银行卡5G前夕专利战再现 爱立信遭国家市监总局突击检查 7优
2 一加7Pro欧洲售价曝光 定制屏幕/皇帝版售价超6000元5步让你的人脉圈更值钱 7优
3 AI体测、智能试衣、智慧客厅,南都记者揭秘“中国品牌日”看点关于解决部分退役士兵社会保险问题的意见印发 7优
4 中考模拟卷被“饿了么”广告植入近五年汽车召回次数 奔驰大众宝马位列前三 7优
5 特斯拉蔚来"着火" 对新能源汽车消费影响几何双色球头奖8注725万分落4地 奖池余额13.07亿 7优
6 焚烧圆明园的除英军还有这些"家贼"@潮州人,这种螺分分钟“要你命”!还有这些食物 7优
7 深扎牛街9年总结的买房地图实力大比拼,宝来、朗逸、雷凌哪家强? 7优
8 深入俄罗斯废弃军事堡垒 揭秘珍贵历史企鹅蛋为何不结冰?帝企鹅爸爸已经进化成"暖水袋" 7优
9 “五一”假期出游安全提示凤凰周刊:如果不靠高考,你该如何体面地出人头.. 7优
10 五一小长假 赴一场艺术之约美军舰通过台湾海峡 外交部:全程掌握有关情况 7优
查看更多>
1 戴森V11体验:干掉扫地机器人央视女主持王小骞41岁意外当妈,如今4岁女儿长这样 381严重
2 社评:中国须拒绝美拉我核裁军的任何念头刘备与诸葛亮,是如何压榨蜀国百姓的? 324严重
3 首条无线充电公路边走边充好嗨呦原来健身房小伙伴可以这么嗨!这部减肥爆笑神剧追定了 273重度
4 九价宫颈癌疫苗的非法商机:一针难求 政策红利解放战争最能打的新四军纵队 270重度
5 女主播直播暴雨 浑身湿透无剧透!《复仇者联盟4》零点场纪实 269重度
6 库里休闲装扮走入球馆 神情轻松自信满满9000元宠物鸡被偷走 找到时已拔光毛准备下锅 257重度
7 日媒:华为在手机芯片领域直追高通中国M99重狙阿勒颇战场发威 256重度
8 京津冀中短途列车“五一”加车百亿营收国企广物控股将和商贸控股合并,曾被巡视组点名整改 229重度
9 唐艺昕撇下男友张若昀,与张一山相约深夜档看大片“乐清失联男孩”母亲一审被判处有期徒刑1年3个月 229重度
10 凤姐怼周立波一文不值、没文化!张镐濂考入上戏安慰洪欣 226重度
查看更多>
>

友情链接: