java源码学习之ThreadPoolExecutor类

上一篇博文学习分享了Thread类的一些源码,本来打算这一周学习分享一下java虚拟机的垃圾回收,但是目前还没有一个好的思路去分享这部分知识,所以,我们暂时先继续学习分享多线程的知识;老规矩,复习一下上篇博文的内容:

①java线程的生命周期

②线程的实现方式

③引起线程等待 WAITTING 的几种方法

然后这篇博文主要包括以下内容点:

①ThreadPoolExecutor类做一个简要的分析。

②类比java.util.concurrent包下的其他“池”。

③浅度的介绍一下happens-before原则。

④站在巨人的肩膀上学习理解ReentrantLock可重入锁。

⑤由重入锁的共享变量可见性探索volatile关键字的线程之间变量可见性的实现机制。

⑥ThreadPoolExecutor中的几种拒绝策略

⑦了解jdk1.8新特性:@FunctionalInterface  函数式接口注解

一、ThreadPoolExecutor

java.util.concurrent包下大致包含了原子性操作类(atomic包)、锁(locks包)、并发容器、几种池。关于线程池相关类,有4种,ForkJoinPool、ScheduledThreadPoolExecutor、ThreadPoolExecutor、Executors类。

ThreadPoolExecutor继承了AbstractExecutorService,AbstractExecutorService实现了ExecutorService接口,ExecutorService接口又继承Executor接口,Executor接口中只定义了一个方法execute(Runnable command)。线程池核心类ThreadPoolExecutor在源码中定义了5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。(“温故”,线程Thread类定义了6种状态:NEW、RUNNABLE、BLOCKED、WAITTING、TIMED_WAITTING、TERMINATED)。runState提供了线程池主要的生命周期控制:

①RUNNING:接受新的任务,处理任务队列中的任务。

②SHUTDOWN:不再接受新的任务,继续处理任务队列中的任务。

③STOP:不再接受新的任务,不再处理队列中的任务,打断正在执行的任务。

④TIDYING:所有任务都已经完成,workerCount值为0,线程转化到TIDYING状态。

⑤TERMINATED:terminated()方法执行完成。

几种状态的关系可以数字化的比较,runState随着线程池运行时间的变化而增加,且不必经过每一个状态:

RUNNING --> SHUTDOWN(调用shutdown方法)

RUNNING or SHUTDOWN --> STOP(调用shutdownNow方法)

SHUTDOWN --> TIDYING(当队列和线程池都为空时)

STOP --> TIDYING(当线程池为空时)

TIDYING --> TERMINATED(当terminated方法执行结果时)

对ThreadPoolExecutor状态的分析,其实就是对线程池生命周期的分析。ThreadPoolExecutor类有4种构造器,根据构造器可以大致的了解到ThreadPoolExecutor类的一些主要属性:

①corePoolSize:核心线程池大小,在不允许空闲工作线程等待的情况下,保持存活工作线程的最小数量。当允许空闲线程等待的情况下,核心线程池大小值可以为0。

②maximumPoolSize:线程池允许的最大线程数量。

③keepAliveTime:线程池空闲工作线程,等待任务的最大时间值,如果当前线程的数量大于核心线程池的数量,空闲线程使用它作为其等待任务的最大时间,或者当属性allowCoreThreadTimeOut为true时,使用keepAliveTime,否则空闲线程会一直等待下去。

④allowCoreThreadTimeOut:默认为false,空闲的核心线程永远存活下去,如果为true,核心线程会以keepAliveTime作为一个等待新任务的最大超时时间。

⑤workQueue:任务队列(BlockingQueue<Runable>),用于存放execute方法提交到线程池的任务,使用isEmpty()方法来判断队列是否为空。

⑥threadFactory:线程工厂(ThreadFactory),创建工作线程的工厂。所有工作线程都是通过addWorker方法创建的。

⑦handler:任务拒绝处理器(RejectedExecutionHandler),当线程池饱和或处于shutdown时,处理器会被使用。

在ThreadPoolExecutor中提供了4种任务拒绝策略,分别为CallerRunsPolicy(任务饱和时,直接由调用execute方法的线程去执行任务,当线程池状态为shutdown时,任务被抛弃)、AbortPolicy(抛出异常,RejectedEexcutionException)、DiscardPolicy(不处理,忽略任务)、DiscardOldestPolicy(抛弃队列中最老的任务,即头任务,重试execute方法,当线程池状态为shutdown时,任务被抛弃)。

当通过execute方法提交了一个新的任务时,如果此时正在运行的工作线程数小于核心线程数,即使其他的线程为空闲的,依然会尝试创建一个新的线程,并将提交的任务作为其第一个任务去执行;如果工作线程数大于核心线程数且小于线程池允许最大线程数,当且仅当任务队列填满的时候,创建一个新的线程;如果我们无法添加一个新的任务时,尝试添加一个线程,如果失败,则根据当前线程池所采用的任务拒绝策略,拒绝任务。

//线程池核心方法
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
	//通过代码可以发现,除了关于线程数量和线程池状态的判断,剩下就是addWorker和reject两个方法了
	//顾名思义,任务的执行应该在addWorker方法中
	
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
			
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
		//上半部分代码依旧是对线程池线程数量和状态的相关判断
		
        boolean workerStarted = false;
        boolean workerAdded = false;
		//工作线程,实现了Runnable接口
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;//引用线程池的可重入锁
				//
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable,检查线程状态,Thread.isAlive()检查线程是否为活跃的,即它已经启动,还没有销毁的状态;
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();//线程启动
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
	
	//工作线程
	private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
		//这里我们只关心它的run方法
		public void run() {
            runWorker(this);
        }
		
		//
		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();//同步锁,
					// If pool is stopping, ensure thread is interrupted;
					// if not, ensure thread is not interrupted.  This
					// requires a recheck in second case to deal with
					// shutdownNow race while clearing interrupt
					if ((runStateAtLeast(ctl.get(), STOP) ||
						 (Thread.interrupted() &&
						  runStateAtLeast(ctl.get(), STOP))) &&
						!wt.isInterrupted())
						wt.interrupt();
					try {
						beforeExecute(wt, task);
						Throwable thrown = null;
						try {
							task.run();
						} catch (RuntimeException x) {
							thrown = x; throw x;
						} catch (Error x) {
							thrown = x; throw x;
						} catch (Throwable x) {
							thrown = x; throw new Error(x);
						} finally {
							afterExecute(task, thrown);
						}
					} finally {
						task = null;
						w.completedTasks++;
						w.unlock();//保证了completedTasks的原子性
					}
				}
				completedAbruptly = false;
			} finally {
				processWorkerExit(w, completedAbruptly);//实现中依旧加锁
				//同步线程池变量completedTaskCount,在任务队列中清除任务
				//completedTaskCount += w.completedTasks;
				//workers.remove(w);
			}
		}
		
		
	}
二、concurrent包下的其他“池”

文章开篇提到了concurrent包下的几种“池”,简要介绍一下ForkJoinPool。ForkJoinPool是java7提供的一个任务并行执行框架,其思想类似于MapReduce思想(Hadoop),就是拆分任务,将任务拆分成多个子任务,如果满足一定条件,子任务可以继续分解出多个子任务,然后将每个子任务计算结果再合并起来。(其实与动态规划算法也有一定的相似之处)要求各个子任务之间相互独立,且能够独立执行任务,互不影响。

ForkJoinPool采用了工作窃取算法,当一个工作线程的任务队列为空,没有任务执行时,便从其他线程中获取任务主动执行。为了实现工作窃取,工作线程中维护了双端队列,窃取任务的线程从队尾获取,被窃取任务的线程从队头获取任务执行,ForkJoin框架同样提供了自己的线程池实现,即ForkJoinPool。提供了三种任务调度的方法:

①execute:无返回结果。

②invoke、invokeAll:异步执行,并等待结果返回。

③submit:异步执行,并立即返回一个Future对象。

ForkJoin框架中实现了自己的任务执行类:

①RecursiveAction:用于无结果返回的子任务

②RecursiveTask:用于有结果返回的子任务

三、ReenTrantLock

关于重入锁,大家可以参考学习java技术栈中周同的博文:http://www.sohu.com/a/209714929_505779

四、happens-before(先行执行原则)

先行发生是Java内存模型中定义的两项操作之间的偏序关系。如果说操作A先行发生于操作B,就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值,发送了消息,调用了方法等。

下面是Java内存模型中存在一些“天然的”先行发生关系,这些发生关系无需任何同步器(AQS,AbstractQueuedSynchronized)协助就已经存在,可以在编码中直接使用,如果2个操作之间的关系不在此列,则它们就没有顺序性保障,虚拟机可以对它们随意的重排序。

①程序次序规则(Program Order Rule),在一个线程内,按照程序代码的顺序,书写在前面的操作先行发生于书写在后面的操作。

②管程锁定规则,一个unlock操作先行发生于后面对同一个锁的lock操作,必须是同一个锁。

③volatile变量原则,对一个volatile变量的写操作先行发生于后面对这个变量的读操作。

④线程启动规则,Thread的start()方法先行发生于此线程的每一个动作。

⑤线程终止规则,线程中的多有操作都先行发生于对此线程的终止检查,可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检查到线程已经终止执行。

⑥线程中断原则,对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

⑦对象终结原则,一个对象的初始化完成先行发生于它的finalize()方法。

⑧传递性,如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。

Java语言无需任何同步手段保障就能成立的先行发生规则就只有这些了。需要注意,一个操作“时间上的先发生”不代表这个操作会先行发生,同样,一个操作的“先行发生”也不代表这个操作必定是“时间上的先发生”,关于此部分的学习思考可以参考上面关于ReentrantLock的链接。此处就不再多说了。

五、关于volatile关键字

关于volatile关键字简简单单概括几句吧,都7点多了,留给我的时间不多了,关键字volatile是Java虚拟机提供的最轻量级的同步机制。当一个变量定义为volatile后,它将具备2中特性:

①保证此变量对所有线程的立即可见性。

②禁止指令重排序优化

但是volatile并不能保证变量的原子性,也就是说在一些场景下并不能保证线程安全,线程安全最终还是要回归到锁机制上。

好了,不多说了,我写的都嫌烦了,更不用多能看到最后的人了。最后一句话常“温故”,必“知新”。

猜你喜欢

转载自blog.csdn.net/soongp/article/details/80092507