手机看天气

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

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

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

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

警方披露抓捕“为刑释者举办欢迎仪式滋事扰序人员”细节

    ”  不过,警方披露虽然这次增资计划搁浅,但蚂蚁金服依然是永安行自行车的重要股东。

    五、抓捕为刑滋事扰序产品分析5.1产品功能结构图 5.2产品版本更新路线2015.8.18——2015.10.26那时候游戏还叫《英雄战迹》,抓捕为刑滋事扰序详细的游戏情况不可考,只知道是主打3V3和闯关、冒险、养成等模式,界面长这样,总之被《全民超神》各方面碾压,最终回炉重造。这样的一个策略,释者举办在端游的时代就没有竞争过真正讲究游戏公平性的MOBA类游戏比如《英雄联盟》和《Dota》,释者举办在手机端这样的一个用户时间更加碎片化的时候,就更不可能能够对移动端的MOBA类游戏产生巨大的威胁。

    警方披露抓捕“为刑释者举办欢迎仪式滋事扰序人员”细节

    2016.1.19新增限时开启的克隆大作战,欢迎仪式新增好友亲密度、观战系统,新增LBS系统,可查看附近的人一起开团,新增排位赛全新荣誉【荣耀王者】。人员细节这在以前的电子游戏中是闻所未闻的。二、警方披露产品简介产品名称:警方披露王者荣耀产品类型:MOBA类大型多人联机在线竞技手游支持平台:IOS/安卓产品logo: 产品slogan:5V5英雄公平对战手游收费模式:售卖点卷,换取英雄、皮肤和其他小道具操作方式:触控和触摸遥感产品介绍:《王者荣耀》是全球首款5V5英雄公平对战手游,包含5V5王者峡谷(含迷雾模式)、5V5深渊大乱斗、以及3V3、1V1等多样模式,同时还有众多英雄可以选择,推搭、补兵、五杀、团战均可体验。这些问题其实本质上是由于《王者荣耀》的目标用户定位而带来的问题,抓捕为刑滋事扰序它的目标用户是小白用户和女性用户,抓捕为刑滋事扰序而且目标人群是极大的,那么根据这些目标用户的操作水平和手机硬件水平,就必然无法设计出非常精密的操作要求和非常精美的画面表现,《王者荣耀》不是不可以设计出来,而是他们选择性的放弃了一部分的操作和画面,因为他们要为他们的目标用户考虑。《王者荣耀》上线后的一个最重要的改进方向就是增加社交的可能性,释者举办打通安卓、释者举办IOS的连接,增加像“微信好友”“LBS荣耀战区”“附近的人”“死党、恋人系统”等等一系列MOBA端游甚至大部分手游里并没有的社交功能,而且这些社交功能基本上都是为了现实生活中的社交而设计的。

    综上,欢迎仪式在版本的迭代记录中,欢迎仪式可以看到《王者荣耀》团队几乎是一个月一次版本和功能的大更新,再加上还需要优化和更新游戏性,同时新增英雄、皮肤,可以说这款游戏虽然只发行了一年多,但是更新的次数却并不少,看来他们团队能够及时针对市场和游戏的目标做出调整和改进,难怪能在短时间之内取得好的成绩。一、人员细节文档概览和分析目的体验机型:人员细节小米MIX系统版本:6.0.1MXB48TApp版本:1.17.1.23体验时间:2017.3.25分析目的:了解《王者荣耀》的产品战略、产品功能和产品表现等产品特性;了解手游市场内的竞争态势和发展方向;分析《王者荣耀》成功的原因及其对应的策略。移动互联网一样的,警方披露如果我们把苹果第一代07年上市的作为移动互联网元年,警方披露中国今天比较火的移动互联网公司基本2011年2012年成立的,同样2011年、2012年也是中国的移动互联网渗透率到达20%。

    对于这样的创业项目,抓捕为刑滋事扰序一开始用户留存低一点没关系,如果能不断提升的话,投资人也是愿意去投的。对VC来讲,释者举办我们看到渗透率是到15%以后可以开始投,10%以上可以关注。document.writeln('关注创业、欢迎仪式电商、站长,扫描A5创业网微信二维码,定期抽大奖。如果你依赖于大的平台获取流量,人员细节集中度非常高的话,会非常难以防御的。

    怎么产生自有流量?靠内容、靠社交媒体,这种流量才是值钱和安全的。现在大家一提到任何一个风口期,往往拿以下数据作为衡量指标:拿到A、B、C轮基本都是在12个月之内。

    警方披露抓捕“为刑释者举办欢迎仪式滋事扰序人员”细节

    中国互联网过去15年,任何一家成功的企业,A轮融资都是非常非常困难、非常痛苦的。做的太早很容易成为先烈,而且,即使你熬到了风口来了以后,这个风口也不是你的,比如很多公司做得比滴滴早,他们也熬到了风口来的时候,但思维、DNA已经不适合那个风口。比如今天的人工智能,所有的巨头都在投,那创业公司在里面能做什么事情?最终大多只能做toB的生意,做几亿美金,十几二十亿美金的公司,但是做到百亿美金几乎不可能。比如说去哪儿,CC(庄辰超)真的是找遍了市面上的所有投资机构,可是没人相信在百度和携程之间还能再出一个平台。

    所以希望大家在创业初期,一定要想清楚自己的这个点是否可防御,否则的话,你培育了市场,最后别人来收割,这是非常痛苦的事情。陌生人交友成立吗?当然,除了这个最重要的因素,拿钱的容易与否,也会影响到创业者下一步的行动,并决定成败。而陌陌为什么可以成功,就是因为自己可以成为流量入口,不需要依赖腾讯、百度。滴滴、饿了么,C轮之前都未进行过大规模的补贴。

    然而过去15年中国成功的互联网创业公司的切入点实际上都很小,去哪儿就是搜索最便宜的机票,滴滴就是打车。而且,他也比较聪明,充分发挥自己的优势,开发了非常强的营销管理系统。

    警方披露抓捕“为刑释者举办欢迎仪式滋事扰序人员”细节

    再拿一笔大钱,扩展20多个城市以上,所以创业一定要把握好这个节奏,当然这是正常情况下的创业节奏,如果碰到资本泡沫,这条路还是必须走下去。中国互联网BAT基本1999年2000年成立的,这是中国互联网的元年。

    (1)首先讲依赖度,对流量和现金的依赖性。所以,对于创业者来说,一定要知道自己在解决什么问题,给用户提供什么价值,而不是只想到一个概念她不仅带领格力突破1000亿的销售额,还带领格力杀入世界五百强。不久后结婚,1982年董明珠生下的儿子冬冬。但是,董明珠这段相夫教子的幸福生活只维持了两年,在董明珠30岁的时候丈夫病逝了,她必须一个人独自承担起上有老下有小的家庭重任。加上安徽市场,董明珠一个人销售额5000万,占了整个公司的1/6!1994年,董明珠再上题解,格力在江苏销售额达到了到1.6亿元,董明珠一个人占了公司总销售额的1/5。

    让世界爱上中国造现在董明珠的理想是“让世家爱上中国造”,她希望世界上的70亿人都能用格力空调,在她看来格力空调是最环保,是可以让人类生活的环境更加美好。同样,李自成进了北京以后,也没有趁胜追击,最终被绞杀。

    生活的压力可想而知,为了能赚到更多的钱,给孩子更好的教育,在儿子8岁的时候,她把孩子托付给母亲抚养,独自一人来到珠海。从那一刻董明珠就下定决定,一定要带领格力杀入世界五百强。

    12年后,2015年,格力进入世界五百强。最开始的时候是为自己而工作,成家之后是为家庭而工作。

    我有一个同事,他在2001年时候手里就有10几套房。而正是他通过投资房地产,他获得了第一笔资金,也发现自己原来更适合经商做生意,他因为压力幸运的找到了属于自己灵感的“1%”。为生存而工作,为家人而战。直到,前天我们两个一起喝茶,他才和我说了实话。

    他当时的收入和我差不多,可是,他却能那么有眼光,利用有限的薪水投资房地产。千万不要小看这个压力的作用,有压力会转化为动力,而这个动力才是成功的核武器。

    同样,对于公司的老板来说,寻找到董明珠身上的特质,找到同样具有这些特质的职业经理人,你的公司也许就是下一个格力,成为下一个世界500强公司。这个时候厂长朱江洪找到董明珠,问她能不能来当销售部部长。

    1万小时的锤炼是任何人从平凡变成世界级大师的必要条件。1984年之前,董明珠过着像中国所有女性一样的传统的生活。

    这个提议足以显现出一个企业家的情怀。她取得的成就,就一个女性而言论是空前的,无人能匹敌。全国两会正在召开,最抢眼的企业家莫过于董明珠,昨天她提出让富人多交税,而让年收入10万元以下者都不缴税,用税收调节来缩小贫富差距。10年后,格力销售额突破了1000亿!如果2003年不是董明珠坚持不以9亿元卖到格力,就没有了现在的格力了,就没有这样一个让我们引以为傲的民族品牌了。

    当公司逐渐强大了,你的格局需要再放大,这个时候是为了国家而工作。你在为谁而工作董明珠为什么要来格力工作?为什么27一直在格力工作?这是一个本源的问题,这个问题决定了董明珠是如何从一个最基层员工成长为公司总裁。

    在巨大的生存压力之下,人的潜能被激发出来。美国第35任总统肯尼迪在就职演讲中说“不要问你的国家能为你做什么,问你能为你的国家做什么?”同样对于一个公司而言也是,不要问公司给你带来了什么,要首先问你给公司做了什么!我想这才是董明珠从公司最基层的一个业务员成长为公司董事长、总裁最重要的原因吧。

    1993董明珠让格力在江苏的销售额翻了10倍,销售额高达3650万元。他说,你知道我为什么要炒房吗?是因为我当时欠了300万的外债,2001年300万外债也是一个天文数字了。

  • 如今全国最低6.37万,本田哥瑞多少人值得拥有? 如今全国最低6.37万,本田哥瑞多少人值得拥有?
  • 苹果反垄断案败诉 商业模式或遭震荡 苹果反垄断案败诉 商业模式或遭震荡
  • 《魔域》如梦长情成长包 《魔域》如梦长情成长包
  • 北京大兴国际机场开始真机验证 北京大兴国际机场开始真机验证
  • 法舰穿台湾海峡非法进入中国领海 法舰穿台湾海峡非法进入中国领海
  • 中国援建柬埔寨国家体育场主体结构封顶 中国援建柬埔寨国家体育场主体结构封顶
  • 伊朗誓言“迎头痛击”美航母 伊朗誓言“迎头痛击”美航母
  • 《自然》呼吁:重视脂肪肝病防治 《自然》呼吁:重视脂肪肝病防治
  • 许嵩:唱过人间的那些情爱 许嵩:唱过人间的那些情爱
  • 美媒:中国向委内瑞拉送援助物资 约200万套医疗用品 美媒:中国向委内瑞拉送援助物资 约200万套医疗用品

城市天气预报

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

国际城市天气预报

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

合作伙伴

气温排行榜

  • 高温
  • 低温
排名 城市 今天气温
1 66家公司营收破千亿 两桶油全年营收逼近浙江省GDP陕西神木一电化企业发生烧伤事故 致2死18伤 23~26 °
2 演员乔杉晒照宣布妻子二胎产女 一家四口合影超温馨五一假期也无休 刘鹤今早会晤美方贸易代表团 23~25 °
3 5.4万游客五一游八达岭长城,景区清理垃圾18.2吨韩国女星张紫妍案出现新证据!案件追诉期有望延长5年 23~25 °
4 提醒!女子被风景"闪瞎"双眼,五一出游,这类人尤其要小心英大臣被召集至议会 将就华为泄密事件回答紧急问题 23~25 °
5 史上最霸气5A景区:混搭全球养殖鸡狗,凯旋门宙斯神庙全都有巴菲特:没有能力看懂亚马逊价值,没投不是我最大的错误 23~25 °
6 你怎么穿这么少?你怎么穿这么多?又到了穿啥都尴尬的季节了缅甸军方宣布延长对民地武停火至6月30日 23~25 °
7 买了火车票却上不了车?原来有人"买短乘长"竟致列车超载花650万美元送女儿上斯坦福的中国富豪:卖药赚十几亿 20~25 °
8 致敬奋斗者—听习近平总书记说给劳动者的"暖心话"中央委员、中将安兆庆已出任武警部队政委 20~25 °
9 美图大甩卖 蔡文胜谋新局青春潜入“深水区”90后公安特警:再浑的水也要蹚 17~25 °
10 国家税务总局:符合条件纳税人可申请退还增量留抵税额漳州年轻母亲留"遗书"带俩幼子出走后续:三人遗体被找到 12~25 °
查看更多>
1 揭秘30元网购假学生证半价游景区 卖家还谎称学信网可查最能"爬梯"的人:张家界"天梯"清洁工一天上下台阶8000步 -24~-12 °
2 游客哄抢展览花?景区回应:是假花,当伴手礼赠送曝中国巨富之女赵雨思花650万美元进斯坦福 直播时称… -24~-9 °
3 二手新能源车卖不上价,行驶1年价格腰斩! 专家:电池衰减北京世园会迎首个客流高峰 京礼高速专用道启用 -23~-12 °
4 俄女记者报道时遭法警察殴打致脑震荡 俄外交部:要彻查陕西神木一电化企业发生烧伤事故 致2死18伤 -23~-8 °
5 以青春之我,创青春中国——写在五四运动100周年之际第十轮中美经贸高级别磋商在京举行 -20~-8 °
6 一季度三大运营商日赚约3.7亿元阿桑奇今日或于英国出庭受审 被指违反保释规定 -20~-8 °
7 习近平与青年谈心时引用的名言隽句历史新高!5月1日全国铁路发送旅客1724.3万人次 -20~-5 °
8 陕西神木一电化企业发生烧伤事故 致2死18伤世界黄金协会:一季度全球央行黄金购买量创六年新高 -19~-10 °
9 江苏:禁止学生将个人手机、平板电脑带入课堂新疆塔什库尔干县发生3.4级地震 震源深度16千米 -18~-12 °
10 警方披露抓捕“为刑释者举办欢迎仪式滋事扰序人员”细节假期前两天中东部晴暖 -18~-10 °
查看更多>

空气质量排行榜

  • 最优
  • 最差
排名 城市 今天空气
1 五一堵车大集锦!堵爆的人群中可有你?游客哄抢展览花?景区回应:是假花,当伴手礼赠送 7优
2 美国北卡罗来纳大学发生枪击案 凶手遭捕时露齿笑大声吼叫世园会提前购票可享优惠 7优
3 外国人说“漏电式”东北话走红雅诗兰黛今起降价 四年内中国区第四次调价 7优
4 习近平祝贺日本新天皇即位"美丽的皇后也无法参加仪式",日本的选票政治就是这么真实 7优
5 五一堵车大集锦!堵爆的人群中可有你?明起冷空气来袭北方变清凉 南方暴雨再现 7优
6 暴风冯鑫:成也「风口」,败也「风口」暴风冯鑫:成也「风口」,败也「风口」 7优
7 银保监会新闻发言人肖远企解读银行业保险业对外开放新措施谷歌遭打击员工再掀抗议 这次Facebook员工也参与进来了 7优
8 中关村“金三角”往事楼市也有涨停板?每月涨幅不得超0.6%这座东北小城调控升级 7优
9 在亚马逊雨林工作的中国工程师 随时都要有颗“大心脏”斯里兰卡总统:爆炸很大可能由外国组织策划 IS开始攻击小国 7优
10 致敬奋斗者—听习近平总书记说给劳动者的"暖心话"挤到窒息!万人排队澳门过关要两小时 现场堪比春运 7优
查看更多>
1 挤到窒息!万人排队澳门过关要两小时 现场堪比春运少年刺死霸凌者五年后 母亲:重判被欺负的孩子就是鼓励施暴 381严重
2 江苏:禁止学生将个人手机、平板电脑带入课堂五一假期各地景区开启人从众模式 网友:动不能动退不能退 324严重
3 阿桑奇违反保释条例被判入狱50周,将抵制美国引渡北京“领跑”绿色发展 273重度
4 4月房企融资计划已近2600亿,拿地亦进入井喷期"美丽的皇后也无法参加仪式",日本的选票政治就是这么真实 270重度
5 Uber上市在即却遭遇司机集体抗议阿桑奇违反保释条例被判入狱50周,将抵制美国引渡 269重度
6 新疆塔什库尔干县发生3.4级地震 震源深度16千米提醒!女子被风景"闪瞎"双眼,五一出游,这类人尤其要小心 257重度
7 以青春之我 创青春中国游客哄抢展览花?景区回应:是假花,当伴手礼赠送 256重度
8 《求是》发表习近平重要文章苹果市值重回万亿 反超微软夺回榜首 229重度
9 以青春之我,创青春中国——写在五四运动100周年之际中东国家没想到,如今会与中国一起做这件事 229重度
10 江苏:禁止学生将个人手机、平板电脑带入课堂数十万游客涌入西湖,只见人头不见断桥 网友:断桥真的要断了 226重度
查看更多>
>

友情链接: