线程池-创建线程

1. 构造函数

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(‘

corePoolSize,

maximumPoolSize,

keepAliveTime,

TimeUnit.SECONDS,

new LinkedBlockingQueue<>(),

new ThreadPoolExecutor.DiscardOldestPolicy());
  1. corePoolSize:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再创建新线程去执行任务。
  2. maximumPoolSize:可能会在核心线程数的基础上,额外增加一些线程,但是这些新增加的线程数有一个上限
  3. keepAliveTime:如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲时间超过keepAliveTime,它们就会被终止
  4. TimeUnit unit

2. 任务存储队列:BlockingQueue<Runnable> workQueue

在新建对象的时候要求传入容量值,且后期不能扩容,所以 ArrayBlockingQueue的最大的特点就是容量是有限的 这样一来,如果任务队列放满了任务,而且线程数也已经达到了最大值,线程池根据规则就会拒绝新提交的任务,这样一来就可能会产生一定的数据丢失。

2.1. 直接交接∶SynchronousQueue

SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除操作take。

2.2. 无界队列∶LinkedBlockingQueue

2.3. 有界的队列∶ArrayBlockingQueue

3. RejectedExecutionHandler handler

  1. 线程池没有能力继续处理新提交的任务,也就是工作已经非常饱和的时候
  2. 当我们调用shutdown等方法关闭线程池后,即便此时可能线程池内部依然有没执行完的任务正在执行,但是由于线程池已经关闭,此时如果再向线程池内提交任务,就会遭到拒绝

AbortPolicy:这种拒绝策略在拒绝任务时,会直接抛出一个类型为RejectedExecutionException的 RuntimeException让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略

DiscardPolicy:这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给 你任何的通知相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失

DiscardOldestPolicy:如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险

CallerRunsPolicy

  • 相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。
  • 新提交的任务不会被丢弃,这样也就不会造成业务损失。
  • 由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当干是一个负反馈。在此期间。线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。

大IO 耗时

为原来执行 IO 任务的线程池使用的是CallerRunsPolicy 策略,所以直接使用这个线程池进行异步计算的话,当线程池饱和的时候,计算任务会在执行 Web 请求的 Tomcat 线程执行,这时就会进一步影响到其他同步处理的线程,甚至造成整个应用程序崩溃

自定义拒绝策略

private static class CustomRejectionHandler implements RejectedExecutionHandler { 
  @Override
  public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 
  //打印日志、暂存任务、重新执行等拒绝策略
  } 
}

4. 线程数多大合适 

  1. CPU密集型(加密、计算hash等)∶最佳线程数为CPU核心数的1-2倍左右。
  2. 耗时IO型(读写数据库、文件、网络读写等)∶最佳线程数一般会大于cpu核心数很多倍,以JVM线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,

基本结论是线程的平均工作时间所占比例越高,就需要越少的线程;

线程的平均等待时间所占比例越高,就需要越多的线程

而对于最大线程数而言,如果我们执行的任务类型不是固定的,比如可能一段时间是 CPU 密集型,另一段时间是 IO 密集型,或是同时有两种任务相互混搭。那么在这种情况下,我们可以把最大线程数设置成核心线程数的几倍,以便应对任务突发情况。当然更好的办法是用不同的线程池执行不同类型的任务,让任务按照类型区分开,而不是混杂在一起。

5. 自定义 ThreadFactory

        当线程池需要新的线程的时候,会使用threadFactory来生成新的线程ThreadFactory 用来创建线程新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有同样NORM_PRIORITY优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。很多第三方的框架也实现的这个接口。

com.google.common.util.concurrent.ThreadFactory

Builder 来实现,如代码所示。

ThreadFactoryBuilder builder = new ThreadFactoryBuilder();

ThreadFactory rpcFactory = builder.setNameFormat("rpc-pool-%d").build();

我们生成了名字为 rpcFactory 的 ThreadFactory,它的 nameFormat 为 "rpc-pool-%d" ,那么它生成的线程的名字是有固定格式的,它生成的线程的名字分别为"rpc-pool-1","rpc-pool-2"

     NamedThreadFactory namedThreadFactory = new NamedThreadFactory("观测元数据", false);
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 5,
            TimeUnit.MINUTES, new SynchronousQueue<>(),
            namedThreadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
    Callable<String> callable=()->{
        Thread.sleep(500);
        return Thread.currentThread().getName()+ "demo";
    };
    ArrayList<Future<String>> futures = new ArrayList<>();
    for (int i = 0; i <100 ; i++) {
        Future<String> submit = threadPoolExecutor.submit(callable);
        futures.add(submit);
    }
    for (Future<String> future : futures) {
        String s = future.get();
        System.out.println(s);
    }
}

6. JDK封装好的构造函数

1. newFixedThreadPool

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

        线程池中的线程数除了初始阶段需要从 0 开始增加外,之后的线程数量就是固定的,就算任务数超过线程数,线程池也不会再创建更多的线程来处理任务,而是会把超出线程处理能力的任务放到任务队列中进行等待。而且就算任务队列满了,到了本该继续增加线程数的时候,由于它的最大线程数和核心线程数是一样的,所以也无法再增加新的线程了由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM

2. CachedThreadPool

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

        在于线程数是几乎可以无限增加的(实际最大可以达到 Integer.MAX VALUE,为 2^31-1,这个数非常大,所以基本不可能达到),而当线程闲置时还可以对线程进行回收。也就是说该线程池的线程数量不是固定不变的,当然它也有一个用于存储提交任务的队列,但这个队列是SynchronousQueue;队列的容量为0,实际不存储任何任务,它只负责对任务进行中转和传递,所以效率比较高

我们知 道线程是需要分配一定的内存空间作为线程栈的,比如 1MB,因此无限制创建线程必然会

导致 OOM

3. newSingleThreadExecutor

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

4. 6.4 ScheduledThreadPool

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
            DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
            new DelayedWorkQueue());
}

4.1. 表示延迟指定时间后执行一次任务,如果代码中设置参数为10秒,也就是10秒后执行一次任务后就结束

 schedule(Runnable command,long delay,TimeUnit unit) 

4.2. 支持定时及周期性任务执行的线程池

scheduleAtFixedRate(Runnable command,long initialDelay,long period,  TimeUnit unit)
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
ScheduledFuture<?> scheduledFuture = threadPool.scheduleAtFixedRate(()->{
    System.out.println("de ");
}, 1, 3, TimeUnit.SECONDS);

5. 不建议使用

  1. 我们需要根据自己的场景、并发情况来评估线程池的几个核心参数,包括核心线程数、最大线程数、线程回收策略、工作队列的类型,以及拒绝策略,确保线程池的工作行为,符合需求,一般都需要设置有界的工作队列和可控的线程数。
  2. 任何时候,都应该为自定义线程池指定有意义的名称,以方便排查问题。当出现线程数量暴增、线程死锁、线程占用大量 CPU、线程执行出现异常等问题时,我们往往会抓取线程栈。此时,有意义的线程名称,就可以方便我们定位问题
  3.  除了建议手动声明线程池以外,我还建议用一些监控手段来观察线程池的状态。线程池这个组件往往会表现得任劳任怨、默默无闻,除非是出现了拒绝策略,否则压力再大都不会抛出一个异常。如果我们能提前观察到线程池队列的积压,或者线程数量的快速膨胀,往往可以提早发现并解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值