关于JAVA线程池关键参数,拒绝策略,调度方式

时间:2020-8-2 作者:admin


     在实际的使用以及面试中时常碰到关于线程池的问题,但是线程池作为Java程序开发中的基础组件
  拥有相当重要的地位,因此结合源码以及代码实验对线程池进行了探究,并用文字记录下来。

如有错误,不妥之处请各位大佬指出

  • 线程池的核心参数
参数 类型 含义
corePoolSize int 核心线程数
maximumPoolSize int 最大并行线程数
keepAliveTime long 非核心线程最大存活时间
unit TimeUnit 描述存活时间的单位
workQueue BlockingQueue 存放任务阻塞队列
threadFactory ThreadFactory 线程工厂
handler RejectedExecutionHandler 拒绝策略

corePoolSize: 线程池中的核心线程数,规定的是线程池中的常驻线程worker 的数量
maximumPoolSize:线程池的线程最大并行数量,规定的是线程池允许的最大的并行线程数量,在核心线程worker 已满 且 队列已满的情况下,会启动非核心的 线程worker 来执行任务
workQueue:核心执行线程已满时用于存放任务的阻塞队列,推荐使用带有边界的ArrayBlockingQueue

线程池最基本的运行单元是线程池中的一个内部内,Worker :
ThreadPoolExecutor extends AbstractExecutorService {
	
	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
	// java.lang.Integer@Native public static final int SIZE = 32;
	//   可见 Integer 是4个byte的长度, 使用了3位来标识线程池的状态,其余位数表示线程池
	//   当前的worker的数量 
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 线程池允许的最大容量,注意 是理论上可以容纳的最大的worker数量,
    // 应该没有大佬会
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
	// 线程池状态标识
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
	//------------------------------------------------------------
	// 获取线程池当前状态    
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    // 获取线程池当前 worker 数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    // 通过位运算获得 线程池状态标识 + 线程池数量的 结果
    private static int ctlOf(int rs, int wc) { return rs | wc; }
    
    /*
   		因为继承了 AbstractQueuedSynchronizer,可知: worker 通过 cas的方式实现了线程安全
   		即 当我在接客的时候 不要往我的房间再塞人
   	*/
	Worker extends AbstractQueuedSynchronizer implements Runnable {
		// worker会从 ThreadFactory中获得一个线程对象
		public void run() {
			// 运行线程
            runWorker(this);
        }
        // ... 其他方法是 创建线程对象,aqs方式对创建的线程进行运行,阻塞,中断等行为的方法
	}
	
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                try {
                	// 线程池增强方法,加入线程池的前置操作,线程池监控常用	
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                    /**
                    调用 runable中的run(),执行业务代码块  
                    **/
                        task.run();
                    } catch (RuntimeException x) {
                    	...
                    } finally {
                    // 线程池增强方法,完成任务之后的后置操作,线程池监控常用
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	// 记录执行结果,并将Worker移除在执行Set集合
            processWorkerExit(w, completedAbruptly);
        }
    }
	
}
  • 线程池调度方式

    线程池的调度,我主要是指线程池当进入一个新任务时是如何完成整个生命周期的,

    新的任务进入 =>
    先查询核心worker数是否已满 ? 未满则新增核心worker执行任务 ,
    已满则查看阻塞队列,阻塞队列未满则加入阻塞队列,
    已满 且 查看最大并行线程数是否已满,
    已满则调用拒绝策略,拒绝接受新增任务
    未满则新增非核心worker处理该任务

    也就是说,在核心线程数以及队列容量没有用完之前,设置的最大线程数是无意义的,线程池不会以最大线程数来并行任务。
    同时,当我们设置阻塞队列的时候 如果采用 阻塞队列时如果 采用无边界的队列,或者不设置边界,在极端情况下对应用是危险的,会因为 Task的堆积发生OOM,这也是阿里大佬在小本本上要求自己去实现ThreadLocalPool 而不使用Executors中提供的线程池的原因。

  • 线程池中的拒绝策略
    实现自己的线程池拒绝策略就是这个接口,在线程池已满的情况下,我们可以把任务放进MQ,Redis,
    应用内部队列中保存起来,找特定的时间窗口再去执行,当然,具体情况具体分析,反正接口,他就在那里

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

JDK大佬提供的拒绝策略:
AbortPolicy(默认):直接报错
DiscardPolicy:不报错,悄悄的就丢了,注意是悄悄的,什么反应都没有
DiscardOldestPolicy:不报错,悄悄的把队列中等得最久得丢了
CallerRunsPolicy:调用者自己处理

  • 使用线程池的注意事项

1,注意初始化线程池时确定合理的并行数
2,不要使用没有边界的队列
3,拒绝策略需结合业务需求慎重选择,当然如果项目规模不大,且并发量低请忽略,默认即可,因为根本走不到那一步,但是咱不能不知道
4,码字不易,如果您老吃得好,欢迎再来,如果要端回家,麻烦给打个标,注明本店位置,下次再见

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。