手机看天气

扫码下载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('首页_点击_实况天气_实况天气')"> 3 °

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

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

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">阴
空气质量74优
未来2小时内无雨~

藏家王珺 :玩儿也是件严肃的事儿

      比如说把50位最顶级投资人的朋友圈地址栏做成一个信息,王珺玩儿我都每天会看,王珺玩儿我就知道他去哪家公司了,这就是资讯的价值,如果定99块钱一定有人买。

    国内手游用户红利渐触天花板,肃的事儿可开发用户范围逐渐紧缩。总结:王珺玩儿虽然《英雄联盟》是《Dota》的简化版,王珺玩儿但他本质上还是一个需要长时间,重度去玩的游戏,所以他的目标人群就只能重点考虑那些理解力强、手速和反应迅速的重度男性游戏玩家,而《王者荣耀》由于定位于手机端,手机硬件和屏幕的限制很难让游戏的设定完全还原《英雄联盟》的游戏体验,所以它必然需要简化,既然需要简化,那么它的用户人群就一定会扩大,既然用户人群会扩大,并且用户人群都是腾讯的,那么玩家的男女比例就会接近1比1,玩家与玩家之间才能非常容易的出现社交因素,既然要出现社交的因素,那么游戏的上手难度就必然要进一步降低,直到能够让小白和女性用户入手,从而达到社交化的用户基数要求。

    藏家王珺 :玩儿也是件严肃的事儿

    类《英雄联盟》游戏的需求:肃的事儿《英雄联盟》等MOBA类竞技游戏在电脑端已经统治了全世界,肃的事儿这充分说明了用户对于此类电子竞技游戏的需求,MOBA类游戏竞技元素多,可玩性高,玩家投入度、用户粘性及忠诚度均较高,团队协作带来的高对抗性、成就感以及归属感能够增加用户粘性,便于后续及周边的持续营销,同时游戏的责任边界模糊,最终使玩家在玩此类游戏时获取的成就感数倍于其他游戏,而遭遇的挫败感又可以忽略不计。貂蝉美,王珺玩儿妲己骚,王珺玩儿韩信帅,李白酷,这就是《王者荣耀》的画面在一般用户心中的印象,由150多人的团队用心打磨出来的《王者荣耀》的皮肤和画风最终受到了用户的喜爱,特别是同时兼顾了男性用户和女性用户的审美。一、肃的事儿文档概览和分析目的体验机型:肃的事儿小米MIX系统版本:6.0.1MXB48TApp版本:1.17.1.23体验时间:2017.3.25分析目的:了解《王者荣耀》的产品战略、产品功能和产品表现等产品特性;了解手游市场内的竞争态势和发展方向;分析《王者荣耀》成功的原因及其对应的策略。而在社交方面,王珺玩儿尽管这是一个MOBA手游,王珺玩儿但团队还是往里面加入了各种各样的社交化的功能,这些社交化的功能是在之前的所有MOBA类游戏中根本没有的,他们早已经发现了社交对于手游的重要性,在传统的PC机端游时代,社交是停留在游戏里面的,游戏里认识的好友现实生活中根本不可能见面,而手游时代的游戏社交则非常不同,手游里的社交不仅仅有游戏内的互动,还有非常大的可能性将社交拉到游戏之外,并且最终使得这个游戏变成现实生活中人与人之间社交的一部分,而《王者荣耀》要走的,就是这样的一条游戏+社交的道路。肃的事儿可以把王者荣耀类比于篮球之类的游戏。

    更巧的是,王珺玩儿和当年的QQ一样,王珺玩儿《英雄联盟》以为只是在手机端多了一个小弟,却没想到这个小弟只用了一年左右就和老大哥平起平坐了,再过个一两年,谁叫谁大哥都还说不定了。2016.5.11新增战队赛玩法,肃的事儿排位赛全段位开启无人排位功能,新增迷雾模式,新增更多装备。不过,王珺玩儿百度这次可不是单纯来刷存在感的,而是带着赤裸裸的目的来的,这得从百度新推的站长平台VIP俱乐部说起,先上图吧。

     据百度站长平台公告,肃的事儿要进入这个VIP俱乐部,是需要有“邀请码”的。从PC时代的凤凰沦落到新媒体时代的“落汤鸡”,王珺玩儿百度太需要存在感来证明自己并不落伍并没被淘汰了,王珺玩儿所以百度从推出百度百家,再到推出升级版百家号,火急火燎、雷声轰轰地在移动端折腾了半天。这可能也算是百度高明的地方,肃的事儿这些鸡肋的小站、肃的事儿自媒体站圈太多了影响用户体验、降低粘性,索性趁机清理门户,只把那些“优质”站点笼络过来就行了。按理说,王珺玩儿百度不应该这么干,王珺玩儿一边想在自媒体时代尽快赶上来,一边又对着一部分“实力不行”的自媒体开刀,其实应该学学那几个自媒体平台啊,别管什么好坏,先把自媒体人圈起来再说。

    第一类,小站以及自媒体站,这是首当其冲的一个群体(1)取消新闻源,对百度来说是件好事。

    藏家王珺 :玩儿也是件严肃的事儿

    (2)对广告主来说,投放软文不好选择了,但选择非新闻源站也有机会进行优质展示了。不仅限于新闻源站点,前提是要有优质内容。问:如何在松松软文里选择优质网站发布?答:大网站、品牌网站、在加上高质量的内容,都有机会进入百度优质展示结果中,可继续参考松松软文里面的“新闻源”一栏。优质原创内容,不再需要进行新闻源的申请,系统将从内容、质量、用户体验等维度判断,对优质内容进行展示。

    如果您想得到更优质的收录与展现,请记住:高质量内容+大网站发布=百度优质搜索结果以下是部分聊天截图:   在推荐一些权威可信赖的网站参考:1.“新华网”、“人民网”、“中新网”等权威新闻机构网站刊登文章;2.“人民日报”、“光明日报”、“北京日报”、“陕西日报”、“南方周末”等中央及各地报纸刊物刊登文章;3.“中国政府网”、“中华人民共和国国防部官方网站”等中央和地方各级政府部门网站刊登文章;4.“中国科学院网站”、“北京大学网站”等公立学术机构、教育机构网站;5.“新浪”、“腾讯”、“搜狐”、“网易”等门户网站刊登文章;6.“36氪”、“时光网”等垂直分类的媒体网站刊登的文章。问:普通网站能否得到类似新闻源的展示效果?答:能,百度取消新闻源后,对很多网站是件好事,但是现在的选择范围更广了,一些不具备条件的网站都有机会进入类似新闻源的展示效果了。原有的优质内容站点,影响并不会太大。不在VIP的站点,内容优质,可以被展示到时效性中;而VIP垂类开放,是针对优质的原创时效性站点,我们将会给到VIP服务,主要看内容是否优质了。

    (3)对站长来说,我的网站都有机会进行优质展示了,是好事。同时也建议您用自己的人写高质量文章,因为自己对自己的产品(业务)会更了解。

    藏家王珺 :玩儿也是件严肃的事儿

    文章来源:松松软文(转载请注明出处)document.writeln('关注创业、电商、站长,扫描A5创业网微信二维码,定期抽大奖。(公告原文)为此,卢松松特地和百度的朋友聊了聊,下划线内都是官方说法:新闻源取消,实际上是百度技术的一次升级和开放,时效性卡片的展示页面不变,后端数据将变得更加开放,不在拘泥于源的申请。

    2017年3月20日,百度站长平台发布公告:百度取消新闻源数据库,升级为VIP俱乐部。常见问题解答:问:松松软文频道里的新闻源还能用吗?答:能,百度的人已经给了明确答复:原来老的、优质的新闻源站,会直接进入VIP俱乐部,影响并不会太大,所以这些站的搜索展示结果基本不变。问:如何提供高质量的内容发布?答:松松软文里有“代写”功能,您可以选择专业写手或职业写手,他们写的文章质量相对较高。问:怎么判断这个站是否是新闻源呢?答:新闻源数据库取消了,但只是换了另一种形式存在,可以继续参考松松软文里面的“新闻源”一栏,选择新闻源站点还是有机会进入百度优质展示的如下图,我们又遇到了错误,显示‘无法添加关键字,因为其中一些已经存在,请删除重复的关键字,然后重试。如果用户中断或直接取消下载/安装过程,这时将不会计入转换。

    记得上个星期我们与大家总结了一下蝉大师海外客户ASM的一些经验与错误,今天我们再接着上个星期的文章,为大家带来ASM投放时常见的十大错误,以帮助大家在ASM正式投放时,可以少走一些弯路,下面我们就来看看具体的内容吧! ASM常见的八大问题:1、确保不超过总支出与每日支出的限额换句话来说就是别超资了,提高时出价尝试2x,3x,4x,5x基线出价,期间要有一定的间隔一些时间,不然监测调价期间的数据时,因为时间间隔太短,会让ASM投放师出现误解。如果搜索查询完全匹配否定关键字,则精确阻止相关搜索词。

    4、为什么我不能再添加任何关键字了苹果总的限制还不清楚,但蝉大师通过试验了解,当我们试图一次性导入几百个甚至一千个关键字时,这个时候上传限制是为每批200个关键字。有两种类型的否定:准确和广泛的匹配。

    广泛配对广告系列,将所有关键字重新添加完全匹配的否定关键字,这会强制广泛匹配以识别新的/同义词/相关搜索组合。一般我们建议是在6至8小时之间逐渐进行。

    从而最终找到全新的搜索组合词,并为无法执行的搜索添加否定关键字,并在完整/广泛匹配的广告系列(以及搜索匹配广告系列的新否定关键字)中添加执行搜索字词。其实这方面最大的用处,是使我们的运营人员可以更便捷的了解我们的用户,关于用户运营方面的知识点,这里就不展开与大家讲解了。搜索匹配广告系列,其中唯一的目标便是匹配没有进行竞价投放的关键词。这里另有几个小知识:确保TTR尽可能高确保关键字拥有一定的流行度尝试创建一个新的广告组与广告系列2、什么是转换根据苹果的文档介绍,转化的意思为:由竞价广告产生的下载次数或重新下载总数”。

    8、否定关键词否定关键字是改善竞价广告系列投放回报率的必需条件。5、为什么搜索竞价的安装次数与第三方工具显示的安装次数不一样这可能是苹果生成的安装下载报告与第三方工具报告存在安装时间上的统计差异,为保证更明确的了解具体数据,建议ASM可以联系第三方工具咨询有关问题。

    本文由蝉大师https://www.chandashi.com/原创,如需转载,请注明出处,否则禁止转载!~~document.writeln('关注创业、电商、站长,扫描A5创业网微信二维码,定期抽大奖。3、如何设置广告系列结构这里介绍一种稍微复杂的方法,这种方法有可能会遇到关键词的限制,下面我们一起来看看。

     7、如何跟踪应用内购买使用第三方平台,并在APP中设置相关自定义归因代码,以跟踪用户在苹果竞价广告里安装应用后所做的一些操作。在广告系列中,将所有其它广告系列的关键词添加为广泛匹配后进行效果监测,通俗的来说就是排除法。

    完全匹配广告系列,只需使用完全匹配关键字,而不使用否定关键字。本文介绍了国外ASM投放师在投放过程中所遇到的八个常见错误,希望大家在看完后,如果自己以后也要进行ASM的投放的话,记得及时规避这些问题,以免给自己带来不必要的麻烦与损失。 苹果搜索广告关键字上传错误经过蝉大师团队的不断测试,我们发现每个广告组的限制为500个关键字,每个广告系列为2,000个关键字。 苹果搜索广告关键字重复错误 最后我们添加的关键字数量最多为389个。

    并熟悉使用各大工具来发现ASM的投放效果,以及关键词竞争热度分析等,这里建议大家使用蝉大师ASM量化托管工具。6、为什么显示与自己关键词无关的搜索?出现这种情况,这可能是由于ASM投放师启用了默认的搜索匹配类型。

    并从其他两个广告系列中,执行搜索字词添加为完全匹配。随后根据关键字的表现,逐渐将搜索字词添加为新的否定关键字

    想要获取最新IT资讯、站长干货分享, 可以关注A5站长网微信公众号。微信自媒体、微信电商的火爆,也成为站长关注的热点。

  • 平安好医生多、空大战背后 平安好医生多、空大战背后
  • 五一假期地铁排队拥挤 黄牛加价倒卖地铁票6人被抓 五一假期地铁排队拥挤 黄牛加价倒卖地铁票6人被抓
  • 下令 下令"要像看脱衣舞那样"欢迎彭斯,美军高官辞职
  • 多名斯里兰卡内阁部长:或有更多自杀式袭击 多名斯里兰卡内阁部长:或有更多自杀式袭击
  • 生在这个日子的人,将来容易富贵双全 生在这个日子的人,将来容易富贵双全
  • 微软被迫“变芯”Chromium 微软被迫“变芯”Chromium
  • 五一假期地铁排队拥挤 黄牛加价倒卖地铁票6人被抓 五一假期地铁排队拥挤 黄牛加价倒卖地铁票6人被抓
  • 29省份晒一季度经济“成绩单” 29省份晒一季度经济“成绩单”
  • 北京一便利店可“狗粪换鸡蛋” 老板:倡导居民文明养狗 北京一便利店可“狗粪换鸡蛋” 老板:倡导居民文明养狗
  • 27个省份一季度GDP数据披露 5地破万亿 27个省份一季度GDP数据披露 5地破万亿

城市天气预报

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

国际城市天气预报

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

合作伙伴

气温排行榜

  • 高温
  • 低温
排名 城市 今天气温
1 英国地方选举初步结果显示两大主要政党纷纷遭重挫法国和意大利总统共同纪念达·芬奇逝世500周年 背后有深意 23~26 °
2 吴鹤臣妻子回应众筹百万:两套房和车不能卖 德云社没有不管中国是不是“对手”?美媒:拜登和特朗普看法都是错的 23~25 °
3 “五一”小长假过半 贵铁警方为旅客寻回失物价值近20万元《国家相册》第二季第2集:青春的接力 23~25 °
4 女子跪求医生帮做人流术后翻脸让赔孩子 医生回应又来挑战我们底线?澳大利亚把台湾列为“国家”跟中国并列 23~25 °
5 高铁香港段治理买短乘长巴医生用受污染注射器致90人感染艾滋病毒 包括65名儿童 23~25 °
6 世园会用大数据“导游”,让游客进得来出得去女子跪求医生帮做人流术后翻脸让赔孩子 医生回应 23~25 °
7 中国积极政策逐渐显效 消费仍是稳增长“压舱石”SpaceX推迟发射龙飞船 此前爆炸原因出炉 20~25 °
8 这个走出多位美国总统的百年组织 竟有1.2万名儿童遭性侵俄罗斯公开展新型反潜航空炸弹投放可后在水面停留4分钟 20~25 °
9 涉嫌重大职务犯罪 潜逃的市政府原副秘书长被抓习近平向泰王国国王哇集拉隆功致加冕贺电 17~25 °
10 担心成靶子!台海巡士兵:出任务最怕配合台军导弹试射餐厅老板带伤料理食物被质疑不卫生 老板:伤口是被烫的 12~25 °
查看更多>
1 铁路部门下发"买短补长"临时办法:执意越站加收50%票款特斯拉致信全体员工不要泄密公司信息 -24~-12 °
2 谱写新时代的青春之歌三大运营商联合整治骚扰电话 要根治还需法律“亮剑” -24~-9 °
3 这个走出多位美国总统的百年组织 竟有1.2万名儿童遭性侵SpaceX推迟发射龙飞船 此前爆炸原因出炉 -23~-12 °
4 面孔丨李想:我是一名特奥体操冠军时隔1年,三星智能机在华占有率重回1% -23~-8 °
5 巴菲特和苹果公司联手推出新APP女副局长发飙后未道歉也没解决问题?教育局回应 -20~-8 °
6 微纪录片 | 无奋斗,不青春SpaceX推迟发射龙飞船 此前爆炸原因出炉 -20~-8 °
7 GitHub遭攻击!黑客给出十天限期乘客被堵火车外 “一直就这样”不是以后也这样的理由 -20~-5 °
8 内蒙古呼伦贝尔市陈巴尔虎旗哈达图地区发生草原火灾金正男之死,今天以后或许永远是个谜了 -19~-10 °
9 《国家相册》第二季第2集:青春的接力驻埃及使馆提醒中国游客斋月期间注意事项 -18~-12 °
10 贪了15年的“明星县委书记” 央视曾播其母街头捡废品罕见!女子竟从河塘中捞起这么个东西!两百万分之一的概率! -18~-10 °
查看更多>

空气质量排行榜

  • 最优
  • 最差
排名 城市 今天空气
1 NASA供应商检测结果造假19年?致两颗7亿美元卫星发射失败那些横空出世又快速陨落的互联网巨头 7优
2 琼海市纪委监委对医院违规接种宫颈癌疫苗启动问责机制武汉一动物园以狗充狼?回应:母狗陪公狼不让它孤单 7优
3 朝鲜试射数枚导弹 飞行距离70-200公里听说新版20元人民币上的渔夫脱单了?真相是…… 7优
4 山西现实版“盲井案”7年11条人命 6名嫌犯4月被枪决乘客被堵火车外 “一直就这样”不是以后也这样的理由 7优
5 涉嫌重大职务犯罪 潜逃的市政府原副秘书长被抓独龙族七十年两次跨越 7优
6 2亿租客可能都关心的事:续约中介费该不该交?《国家相册》第二季第2集:青春的接力 7优
7 玉龙雪山发生山体岩石崩塌 官方通报:不属于旅游区范围这个地图上不存在的绝密地方 我们去了! 7优
8 步长制药董事长赵涛:女儿留学资金与公司无关SpaceX推迟发射龙飞船 此前爆炸原因出炉 7优
9 要不要建大型对撞机?杨振宁:盛宴已过!第二届数字中国建设峰会 7优
10 雷军自曝25年前旧照 网友发现端倪Uber和Lyft司机大面积“停摆”困局难破 7优
查看更多>
1 再上25天班又是小长假 今年还有这些假期在等你!铁路部门下发"买短补长"临时办法:执意越站加收50%票款 381严重
2 银行员工私转客户百余万存款潜逃 法院判银行无责琼海市纪委对“海南一医院违规接种九价宫颈癌疫苗”问责 324严重
3 戏精本精!假药贩子谎称能激活人类长寿密码 演完主任演教授"宠粉"重庆的基本操作:五一节封桥给游客看夜景拍照 273重度
4 14万民众向日本新天皇朝贺 121人中暑在爆挤景点喊“我想回家”走红 当事人:以后选人少地方 270重度
5 冲突升级!以色列回击加沙火箭弹袭击 致4人死亡电信向移动学习:推出查网龄送优惠 269重度
6 美国检方要求取消首席辩护律师资格 华为强烈抗议美国检方要求取消首席辩护律师资格 华为强烈抗议 257重度
7 "宠粉"重庆的基本操作:五一节封桥给游客看夜景拍照中国游客在巴厘岛遭性侵 领馆通报 256重度
8 世界需要共建一带一路苹果或将大规模生产iPhone XE 229重度
9 爬黄山挤哭,小学生吐槽不如回家写作业4612桌正在排队!网友哀嚎:假都放完了 还没轮到我吃饭 229重度
10 国家卫健委:应尽量避免学龄前儿童使用手机和电脑第二届数字中国建设峰会 226重度
查看更多>
>

友情链接: