Java 中线程池的原理与源码浅析

并发编程 中,我们只介绍了几种常用的线程池,以及它们的使用,那线程池是如何管理一组线程的呢?


介绍

先来看下关系图:
在这里插入图片描述图片来源网络

从图中我们可以看出,之前我们学习的几个常用的线程池 CachedThreadPool、FiedThreadPool 等都是通过 Executors 调用创建的:

	ExecutorService pool = Executors.newCachedThreadPool();
	pool.execute(MyRunnable); 

而 Excutors 其实是一个工具类,为我们提供了创建三种典型线程池的方法,内部使用的是 ThreadPoolExcutor,最后调用的 ThreadPoolExecutor 的构造方法来创建线程池。
在这里插入图片描述


ThreadPoolExcutor

ThreadPoolExcutor 是线程池体系中的核心,那么先来看下它的构造方法以及构造参数:

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize: 线程池中核心线程的数量;当提交一个任务到线程池的时候,线程池会创建一个线程来执行执行任务,即使有其他空闲的线程存在,直到线程数达到corePoolSize时不再创建。
  • maximumPoolSize: 线程池允许创建的最大线程数;如果阻塞队列已经满了,同时已经创建的线程数小于最大线程数的话,那么会创建新的线程来处理阻塞队列中的任务。
  • keepAliveTime: 线程没有任务处理时,还可以存活多久。(和unit参数配合生效,unit是单位,keepAliveTime是数值)。
  • unit: 时间单位,比如秒,分等
  • workQueue: 阻塞队列;用于存储等待执行的任务,有四种阻塞队列类型,ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue。
  • threadFactory: 线程工厂,用来创建线程,主要是为了给线程起名字。
  • handler: 线程池达到最大线程数之后,再来任务,由这个handler来执行具体拒绝策略。

默认提供的四种线程池

  • CachedThreadPool: 缓存线程池,该类线程池中线程的数量是不确定的,理论上可以达到Integer.MAX_VALUE个,这种线程池中的线程都是非核心线程,既然是非核心线程,那么就存在超时淘汰机制了,当里面的某个线程空闲时间超过了设定的超时时间的话,就会回收掉该线程;
  • FixedThreadPool: 固定线程池,这类线程池中是只存在核心线程的,对于核心线程来说,如果我们不设置allowCoreThreadTimeOut属性的话是不存在超时淘汰机制的,这类线程池中的corePoolSize的大小是等于maximumPoolSize大小的,也就是说,如果线程池中的线程都处于活动状态的话,如果有新任务到来,他是不会开辟新的工作线程来处理这些任务的,只能将这些任务放到阻塞队列里面进行等到,直到有核心线程空闲为止;
  • ScheduledThreadPool: 任务线程池,这种线程池中核心线程的数量是固定的,而对于非核心线程的数量是不限制的,同时对于非核心线程是存在超时淘汰机制的,主要适用于执行定时任务或者周期性任务的场景;
  • SingleThreadPool: 单一线程池,线程池里面只有一个线程,同时也不存在非核心线程,感觉像是FixedThreadPool的特殊版本,他主要用于确保任务在同一线程中的顺序执行,有点类似于进行同步吧;

创建出线程池后,就是我们 excute() 方法,将我们的任务添加到线程池中

		public void execute(Runnable command) {
	        if (command == null)
	            throw new NullPointerException();
	            
	        int c = ctl.get();
	        //如果线程池当前线程数小于核心线程数,则创建新线程处理。
	        if (workerCountOf(c) < corePoolSize) {
	        	//通过addWorker方法创建一个新的Worker对象来执行我们当前的任务;
	            if (addWorker(command, true))
	                return;
	            c = ctl.get();
	        }
	        //如果核心线程都满了,则尝试把新来的任务放到阻塞队列里
	        if (isRunning(c) && workQueue.offer(command)) {
	            int recheck = ctl.get();
	            //如果线程池不在Running状态的话,会将刚刚添加到阻塞队列中的任务移出,同时拒绝当前任务请求
	            if (! isRunning(recheck) && remove(command))
	                reject(command);
	            //当前线程池中的工作线程数量是否为0,如果为0的话,就会通过addWorker方法创建一个Worker对象出来处理阻塞队列中的任务
	            else if (workerCountOf(recheck) == 0)
	                addWorker(null, false);
	        }
	        //放入阻塞队列失败(队列满了),就再创建新线程进行处理,这里addWorker第二个参数为false,表示非核心线程
	        else if (!addWorker(command, false))
	            reject(command);//阻塞队列满了,线程数也达到最大了,则执行拒绝策略
    	}

当达到最大线程数且阻塞队列满了时,再添加任务就会执行拒绝策略,主要有四种都在 ThreadPoolExcutor 中定义的静态内部类:

  • CallerRunsPolicy: 直接使用调用该execute的线程本身来执行
		public static class CallerRunsPolicy implements RejectedExecutionHandler {
	        public CallerRunsPolicy() { }
	
	        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	            if (!e.isShutdown()) {
	                r.run();
	            }
	        }
	    }
  • AbortPolicy: 直接抛出RejectedExecutionException异常,丢弃任务
		public static class AbortPolicy implements RejectedExecutionHandler {
	        public AbortPolicy() { }
	
	        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	            throw new RejectedExecutionException("Task " + r.toString() +
	                                                 " rejected from " +
	                                                 e.toString());
	        }
	    }
  • DiscardPolicy: 丢弃任务,但是不会抛出异常
		public static class DiscardPolicy implements RejectedExecutionHandler {
	        public DiscardPolicy() { }

	        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	        }
	    }
  • DiscardOldestPolicy: 如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序
		public static class DiscardOldestPolicy implements RejectedExecutionHandler {
	        public DiscardOldestPolicy() { }

	        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
	            if (!e.isShutdown()) {
	                e.getQueue().poll();
	                e.execute(r);
	            }
	        }
	    }

为任务分配线程后,线程池又是如何实现线程的复用呢?
默认情况下,核心线程不会销毁,会一直尝试从阻塞队列里取任务去执行,除非使用 allowCoreThreadTimeOut 设置超时淘汰;
而非核心线程如果没有任务要处理,会在60s之后销毁。而如果在60s之内从阻塞队列里取到任务了,就会继续执行任务。

线程池怎么取任务?在我们提交一个任务时,调用 ThreadPoolExecutor 的 execute() 方法-> addWorker() -> addWorker() 里的 t.start() -> Worker的run()方法 -> runWorker(),而 runWorker 中 while 循环的条件中的 getTask()就是从阻塞队列中获取任务并进行处理的:
在这里插入图片描述
getTask(),从 workQueue 中获取:
在这里插入图片描述


最后就是线程的关闭了,涉及两个方法:

  • shutdown()
		public void shutdown() {
	        final ReentrantLock mainLock = this.mainLock;
	        mainLock.lock();
	        try {
	            checkShutdownAccess();
	            advanceRunState(SHUTDOWN);
	            interruptIdleWorkers();
	            onShutdown(); 
	        } finally {
	            mainLock.unlock();
	        }
	        tryTerminate();
	    }
  • shutdownNow()
		public List<Runnable> shutdownNow() {
	        List<Runnable> tasks;
	        final ReentrantLock mainLock = this.mainLock;
	        mainLock.lock();
	        try {
	            checkShutdownAccess();
	            advanceRunState(STOP);
	            interruptWorkers();
	            tasks = drainQueue();
	        } finally {
	            mainLock.unlock();
	        }
	        tryTerminate();
	        return tasks;
	    }

advanceRunState 方法设置线程池的状态,有四种状态:
在这里插入图片描述
对应四个状态:

  • RUNNING:可以接受新任务,同时也可以处理阻塞队列里面的任务
  • SHUTDOWN:不可以接受新任务,但是可以处理阻塞队列里面的任务
  • STOP:不可以接受新任务,也不处理阻塞队列里面的任务,同时还中断正在处理的任务
  • TIDYING:属于过渡阶段,在这个阶段表示所有的任务已经执行结束了,当前线程池中是不存在有效的线程的,并且将要调用terminated方法
  • TERMINATED:终止状态,这个状态是在调用完terminated方法之后所处的状态

shutdown中的interruptIdleWorkers:
在这里插入图片描述
shutdownNow中的interruptWorkers:
在这里插入图片描述
可以看到都是调用 interrupt() 方法,主要区别是:在 shutdown 的判断条件中,先尝试获取锁:
在这里插入图片描述
而一个任务在执行时是,上锁的状态,所以 shutdown 不会关闭还在执行的任务。

区别:

  • shutdown():线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池
  • shutdownNow():线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值