java线程池详解

1.为什么使用线程池

1.频繁创建和销毁单个线程,浪费资源,并且还会出现频繁GC
2.缺乏统一管理,各线程相互竞争

2.ThreadPoolExecutor

ThreadPoolExecutor有四个重载的构造方法,我们这里来说说参数最多的那一个重载的构造方法,这样大家就知道其他方法参数的含义了,如下:


public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

1.各参数详细说明:
这里是7个参数(我们在开发中用的更多的是5个参数的构造方法),OK,那我们来看看这里七个参数的含义:

corePoolSize 线程池中核心线程的数量

maximumPoolSize 线程池中最大线程数量

keepAliveTime 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长

unit 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等

workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。

threadFactory 为线程池提供创建新线程的功能,这个我们一般使用默认即可

handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。

2.workQueue介绍
1.ArrayBlockingQueue:这个表示一个规定了大小的BlockingQueue,ArrayBlockingQueue的构造函数接受一个int类型的数据,该数据表示BlockingQueue的大小,存储在ArrayBlockingQueue中的元素按照FIFO(先进先出)的方式来进行存取。

2.LinkedBlockingQueue:这个表示一个大小不确定的BlockingQueue,在LinkedBlockingQueue的构造方法中可以传一个int类型的数据,这样创建出来的LinkedBlockingQueue是有大小的,也可以不传,不传的话,LinkedBlockingQueue的大小就为Integer.MAX_VALUE,源码如下:

3.PriorityBlockingQueue:这个队列和LinkedBlockingQueue类似,不同的是PriorityBlockingQueue中的元素不是按照FIFO来排序的,而是按照元素的Comparator来决定存取顺序的(这个功能也反映了存入PriorityBlockingQueue中的数据必须实现了Comparator接口)。

4.SynchronousQueue:这个是同步Queue,属于线程安全的BlockingQueue的一种,在SynchronousQueue中,生产者线程的插入操作必须要等待消费者线程的移除操作,Synchronous内部没有数据缓存空间,因此我们无法对SynchronousQueue进行读取或者遍历其中的数据,元素只有在你试图取走的时候才有可能存在。我们可以理解为生产者和消费者互相等待,等到对方之后然后再一起离开。

3.拒绝策略

AbortPolicy:直接拒绝,并抛出异常,这也是默认的策略。
CallerRunsPolicy:直接让调用execute方法的线程去执行此任务。
DiscardOldestPolicy:丢弃最老的未处理的任务,然后重新尝试执行当前的新任务。
DiscardPolicy:直接丢弃当前任务,但是不抛异常

3.执行过程

当线程数量未达到corePoolSize的时候,就会创建新的线程来执行任务。
当核心线程数已满,就会把任务放到阻塞队列。
当队列已满,并且未达到最大线程数,就会新建非核心线程来执行任务(重要)。
当队列已满,并且达到了最大线程数,则选择一种拒绝策略来执行。

4.其他线程池

1.FixedThreadPool

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

固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。
该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。
同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。
而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown

2.SingleThreadExecutor

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

可以看到阻塞队例 使用的是LinkedBolckingQueue,且默认大小为Integer.MAX_VALUE,这样的话,如果有大量请求到来,会放入到这个任务队列里,可能会导致OOM;

3.Executors.newCachedThreadPool()

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

可缓存线程池,先查看线程池中有没有以前建立的线程,如果有就直接使用,如果没有新建一个线程加入线程池中,可缓存线程池
通常用于执行一些生存期很短的异步型任务;线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程

缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。
是一个直接提交的阻塞队列,他总会迫使线程池增加新的线程去执行新的任务。
在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,
如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。
如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。

4.ScheduledThreadPool

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

创建一个定长线程池,支持定时及周期性任务执行
定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。
schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。

5.为什么阿里推荐自定义线程池

通过上述源码分析,我们发现newFixedThreadPool和newSingleThreadExecutor方法他们都使用了LinkedBlockingQueue的任务队列,LinkedBlockingQueue的默认大小为Integer.MAX_VALUE。而newCachedThreadPool中定义的线程池大小为Integer.MAX_VALUE。

所以阿里禁止使用Executors创建线程池的原因就是FixedThreadPool和SingleThreadPool的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

CachedThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

6.其他

1.shutDown() 关闭线程池,不影响已经提交的任务

2.shutDownNow() 关闭线程池,并尝试去终止正在执行的线程

3.allowCoreThreadTimeOut(boolean value) 允许核心线程闲置超时时被回收

7.单例模式创建线程池

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.*;

/**
 * 异步任务处理器
 */
public class AsyncTaskExecutor {

    /** 线程池保持ALIVE状态线程数 */
    public static final int                 CORE_POOL_SIZE      = 10;

    /** 线程池最大线程数 */
    public static final int                 MAX_POOL_SIZE       = 40;

    /** 空闲线程回收时间 */
    public static final int                 KEEP_ALIVE_TIME     = 1000;

    /** 线程池等待队列 */
    public static final int                 BLOCKING_QUEUE_SIZE = 1000;

    /** 业务请求异步处理线程池 */
    private static final ThreadPoolExecutor processExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.MICROSECONDS,
            new LinkedBlockingQueue<Runnable>(BLOCKING_QUEUE_SIZE),
       new TreadFactoryBuilder.setNameFormat("boomoom-thread-pool-%d").build(),
       new TreadPoolExecutor.DiscardPolicy());

    private AsyncTaskExecutor() {}; 

    /**
     * 异步任务处理
     *
     * @param task 任务
     */
    public void execute(Runnable task) {
        processExecutor.submit(task);
    }

}

懒汉式和饥汉式区别
1.饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。
2.懒汉式如果想要线程安全必须用双重检验锁并且对象还必须是volatile,防止对象指令重排

参考:https://www.cnblogs.com/boomoom/p/9290311.html
https://blog.csdn.net/cj_eryue/article/details/115197013
https://segmentfault.com/a/1190000021657959

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Java线程池是一种用于管理和复用线程的机制,它可以提高多线程程序的性能和效率。在Java中,线程池由ThreadPoolExecutor类实现,通过设置不同的参数可以对线程池的行为进行调整。 以下是Java线程池的一些常用参数及其解释: 1. corePoolSize(核心线程数):线程池中始终保持的活动线程数,即使它们处于空闲状态。当有新任务提交时,如果活动线程数小于corePoolSize,则会创建新线程来处理任务。 2. maximumPoolSize(最大线程数):线程池中允许存在的最大线程数。当活动线程数达到maximumPoolSize并且工作队列已满时,新任务将会被拒绝。 3. keepAliveTime(线程空闲时间):当线程池中的线程数量超过corePoolSize时,多余的空闲线程在等待新任务到来时的最长等待时间。超过这个时间,空闲线程将被终止。 4. unit(时间单位):keepAliveTime的时间单位,可以是秒、毫秒、微秒等。 5. workQueue(工作队列):用于存储等待执行任务的阻塞队列。常见的工作队列有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等。 6. threadFactory(线程工厂):用于创建新线程的工厂类。可以自定义线程的名称、优先级等属性。 7. handler(拒绝策略):当线程池无法接受新任务时的处理策略。常见的拒绝策略有AbortPolicy(默认,抛出RejectedExecutionException异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)和DiscardOldestPolicy(丢弃最旧的任务)。 这些参数可以根据实际需求进行调整,以达到最佳的线程池性能和资源利用率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值