首页 文章 精选 留言 我的

精选列表

搜索[源码阅读],共10000篇文章
优秀的个人博客,低调大师

0源码基础学习Spring源码系列(二)——Spring如何解决循环依赖

作者:京东科技 韩国凯 1.1 解决循环依赖过程 1.1.1 三级缓存的作用 循环依赖在我们日常开发中属于比较常见的问题,spring对循环依赖做了优化,使得我们在无感知的情况下帮助我们解决了循环依赖的问题。 最简单的循环依赖就是,A依赖B,B依赖C,C依赖A,如果不解决循环依赖的问题最终会导致OOM,但是也不是所有的循环依赖都可以解决,spring只可以解决通过属性或者setter注入的单例bean,而通过构造器注入或非单例模式的bean都是不可解决的。 通过上文创建bean的过程中我们知道,在获取bean的时候,首先会尝试从缓存中获取,如果从缓存中获取不到才会去创建bean,而三层的缓存正是解决循环依赖的关键: protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock //从一级缓存中加载 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //从二级缓存中加载 singletonObject = this.earlySingletonObjects.get(beanName); //allowEarlyReference为true代表要去三级缓存中查找,此时为true if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { //从三级缓存中加载 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; } 可以看到三层缓存其实就是三个hashmap: /** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); 三级缓存的作用: singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用 earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖 1.1.2 解决循环依赖的流程 根据上文,我们都知道创建bean的流程主要包括以下三步: 实例化bean 装配bean属性 初始化bean 例如我们现在有 A依赖B,B依赖A,那么spring是如何解决三层循环的呢? 首先尝试从缓存中加载A,发现A不存在 实例化A(没有属性,半成品) 将实例化完成的A放入第三级缓存中 装配属性B(没有属性,半成品) 尝试从缓存中加载B,发现B不存在 实例化B 将实例化完成的B放入第三级缓存中 装配属性A 尝试从缓存中加载A,发现A存在于缓存中(第3步),将A从第三级缓存中移除,放入第二级缓存中,并将其赋值给B,B装配属性完成 此时B的装配属性完毕,初始化B,并将B从三级缓存中移除,放入一级缓存 返回第四步,此时A的属性也装配完成 初始化A,并将A放入一级缓存 自此,实例A于B都分别完成了创建的流程。 用一张图来描述: 那么此时有一个问题,在第9步中B拥有的A是只实例化完成的对象,并没有属性装配以及初始化,A的初始化是在11步以后,那么在最后全部创建完成此时B中的的属性A是半成品还是已经可以正常工作的成品呢?答案是成品,因为B对A可以理解为引用传递,也就是说B中的属性A于第11步之前的A为同一个A,那么A在第11步完成了属性装配,自然B中的属性也会完成属性装配。 例如我们在一个方法中传入一个实例化对象,如果在方法中对实例化对象做了修改,那么在方法结束后该实例化对象也会做出修改,需要注意的是实例化对象,而不是java中的几种基本对象,基本对象是属于值传递(其实实例化对象也是值传递,不过传入的是对象的引用,可以理解为引用传递)。 private static void fun3(){ Student student = new Student(); student.setName("zhangsan"); System.out.println(student.getName()); changeName(student); System.out.println(student.getName()); String str = "zhangsan"; System.out.println(str); changeString(str); System.out.println(str); } private static void changeName(Student student){ student.setName("lisi"); } private static void changeString(String str){ str = "lisi"; } //输出结果 zhangsan lisi zhangsan zhangsan 可以看出引用传递会改变对象,而值传递不会。 2.2 为什么是三层缓存 2.2.1 三级缓存在循环依赖中的作用 有的小伙伴可能已经注意到了,为什么需要三层缓存,两层缓存好像就可以解决问题了,然而如果不考虑代理的情况下确实两层缓存就能解决问题,但是如果要引用的对象不是普通bean而是被代理的对象就会出现问题。 大家需要知道的是,spring在创建代理对象时,首先会实例化源对象,然后在源对象初始化完成之后才会获取代理对象。 我们先不考虑为什么是三级缓存,先看一下在刚才的流程中代理对象存在什么问题 回到我们刚刚举的例子,加入现在我们需要代理对象A,其中A依赖于B,而B也是代理对象,如果不进行特殊处理的话会出现问题: 首先尝试从缓存中加载A,发现A不存在 实例化A(没有属性,半成品) 将实例化完成的A放入第三级缓存中 装配属性B(没有属性,半成品) 尝试从缓存中加载B,发现B不存在 实例化B 将实例化完成的B放入第三级缓存中 装配属性A 尝试从缓存中加载A,发现A存在于缓存中(第3步),将A从第三级缓存中移除,放入第二级缓存中,并将其赋值给B,B装配属性完成 此时B的装配属性完毕,初始化B,并将B从三级缓存中移除,放入一级缓存 返回第四步,此时A的属性也装配完成 初始化A,并将A放入一级缓存 跟之前一样的流程,那么此时B拥有的对象是A的普通对象,而不是代理对象,这就有问题了。 可能有同学会问,不是存在引用传递吗?A被代理完成不是还是会被B所拥有吗? 但是答案也很简单,并不是,A跟A的代理对象肯定时两个对象,在内存中肯定也是两个地址,因此需要解决这种情况。 2.2.2 解决代理对象的问题 我们来看看spring是如何解决这个问题的: 根据bean创建的过程我们知道,bean会首先被实例化,在实例化完成之后会执行这样一段代码: //3.是否需要提前曝光,用来解决循环依赖时使用 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } 这也是我们2.2.5(详见上篇文章)中的代码,主要有两部分 首先会判断是否需要提前曝光,判断结果由三部分组成,分别是: 是否是单例模式,默认情况下都是单例模式,spring也只能解决这种情况下的循环依赖 是否允许提前曝光,默认是true,也可以更改 是否正在创建,正常来说一个bean在创建开始该值为true,创建结束该值为false 可以看到正常情况下一个bean的这些结果都为true,也就是会进入到下面的方法中,该方法中有一个lamda表达式,为了可读性这里进行了拆分。 ObjectFactory<Object> objectFactory = new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } } ; addSingletonFactory(beanName, objectFactory); 该方法的主要内容就是创建了一个ObjectFactory,其中getObject()方法返回了getEarlyBeanReference(beanName, mbd, bean)这个方法调用结果。 看看addSingletonFactory()这个方法都干了什么 protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { //向三级缓存中添加内容 this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } } 其中最主要的一行代码就是向三级缓存中添加了对象,而添加的对象就是传入的objectFactory,注意,此处添加的不是bean,而是factory。这也是我们上文解决循环依赖过程中第三步的操作。 加入存在循环依赖的话,此时A已经被加入到缓存中,当A被作为依赖被其他bean使用时,按照我们之前的逻辑会调用缓存 //从三级缓存中加载 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //从刚刚添加的objectFactory获取其中的对象,也就是调用getObject,也就是获取getEarlyBeanReference方法的内容 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } 从刚刚添加的objectFactory获取其中的对象,也就是调用getObject,也就是获取getEarlyBeanReference方法的内容 那么我们看看为什么会返回一个factory而不是一个bean exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); return wrapIfNecessary(bean, beanName, cacheKey); 根据链路可以看到,最终调用到这个方法中返回的,也就是说当实例A被别的bean依赖时,返回的其实是这个方法中的结果。 protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // 如果是一个需要被代理对象的话,会在此处返回被代理的对象 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } 看到这里应该就是一目了然了spring是如何处理存在代理对象且存在循环依赖的情况的。 回到之前的逻辑,例如此时实例B需要装填属性A,会从缓存中查询A是否存咋,查询到A已经存在,则调用A的getObject()方法,如果A是需要被代理的对象则返回被代理过得对象,否则返回普通bean。 此外还需要注意的是:先创建对象,再创建代理类,再初始化原对象,和初始化之后再创建代理类,是一样的,这也是可以提前暴露代理对象的基础。 2.2.3 二级缓存的作用 那么二级缓存是干什么用的呢? addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 可以在源代码中看出每调用一次getObject()然后调用getEarlyBeanReference()中createProxy()都会产生一个新的代理对象,并不不符合单例模式。 (网上有很多文章说是因为调用lamda表达式所以会产生新的对象,其实如果非代理bean并不会产生新的对象,因为objectFactory所持有的是有原始对象的,即时多次调用也会返回相同的结果。但是对于代理对象则会每次新create一个,所以其实会产生新的代理对象而不会是新产生普通对象。所以就其本质为什么使用二级缓存的原因是因为创建代理对象是使用createProxy()的方法,每次调用都会产生新的代理对象,那么其实只要有一个地方能根据beanName返回同一个代理对象,也就不需要二级缓存了,这也是二级缓存的本质意义。其实也可以在getObject()方法中去缓存创建完成的代理对象,只不过这样做就太不优雅,不太符合spring的编码规范。 ) Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); 例如A依赖于B,B依赖与A、C、D,而C、D又依赖于A,如果不进行处理的话,A实例化完成之后,在B创建过程中获取A的代理对象A1,然后C、D获取的代理对象就是A2、A3,显然不符合单例模式。 因此需要有一个地方存储从factory中获取到的对象,也就是二级缓存的功能: if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); //存储获取到的代理对象或普通bean this.earlySingletonObjects.put(beanName, singletonObject); //此时三级缓存的工厂已经没有意义了 this.singletonFactories.remove(beanName); } 2.2.4 代理对象什么时候被初始化的 本来以为到这里就结束了,但是在梳理有代理对象的循环依赖时,突然又发现一个问题: 还是A、B都有代理互相依赖的例子,在B装配完A的代理对象后,B初始化完成,A开始初始化,但是此时的A是原始beanA1,并不是代理对象A2,而B持有的是代理对象A2,那么原始对象A1初始化A2并没有初始化,这不是有问题的吗? 在经过一天的查找以及搜寻资料还有debug后,终于在一篇文章中到答案: 不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化 也就是说原始对象A1初始化完成后,因为A2是对A1的封装以及增强,也就代表着A2也完成了初始化。 2.2.5 什么时候返回代理对象 此外还有一点需要注意的是,在A1装配完之后,以后其他bean依赖的应该是A2,并且加入到一级缓存中的也应该是A2,那么什么时候A1被换成A2的呢? //在上方已经判断过,一般为true if (earlySingletonExposure) { //1.注意此处传入的是false,并不会去三级缓存中查找,并且如果是代理对象的话此时会返回代理对象 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { //2.判断经过后置处理器后对象是否被改变 ==的话说明没有被改变 那么如果是代理对象的话返回被代理的bean if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } 以上有两个需要注意的地方: 因为在之前在B装配属性A的时候,即从三级缓存中查找A的时候,如果查到了会将A的bean(有可能是代理对象)放入二级缓存,然后删除三级缓存,那么此时getSingleton()返回的就是二级缓存中的bean。 这里判断经过后置处理器的bean是否被改变,一般是不会改变的,除非实现BeanPostProcessor接口手工改变。如果没有改变的话则将缓存中的数据取出返回,也就是说如果此时二级缓存中的是代理beanA2,此时会返回A2而不是原始对象A1,如果是普通bean的话则都一样。而如果对象已经被改变则走下面的判断有没有可能报错的逻辑。 3.1循环依赖总结 在学习代理在循环依赖的,发现其实并不太需要二级缓存,可以在bean实例化完成之后就选择要不要生成代理对象,如果要生成的话就往三级缓存中放入代理对象,否则的话就放入普通bean,这样别人过来拿的时候就不用判断是否需要返回代理对象了。 后面发现在网络上有很多跟我想得一样的人,目前参考别人的想法以及自己进行了总结大概是这样子的: 无论是实例化完成之后就进行对象代理还是选择返回一个factory在使用的时候进行代理其实效率上都没有什么区别,只不过一个是提前做一个是滞后做,那么为什么spring选择滞后做的这件事呢?我自己的思考是: 道理也很简单,既然效率没有什么提升的话,为什么要破坏普通bean的创建流程,本来循环依赖就是一件非常小概率的事,没必要因为小概率事情并且滞后也可以解决,从而选择需要修改普通bean的创建逻辑,这无异于是本末倒置,而这也是二级缓存或者说三级缓存中存放的是factory的意义。

优秀的个人博客,低调大师

Java源码分析专题系列之【ThreadPoolExecutor】深入浅出的源码分析

线程池执行任务的流程 如果线程池工作线程数<corePoolSize,创建新线程执行task,并不断轮训t等待队列处理task。 如果线程池工作线程数>=corePoolSize并且等待队列未满,将task插入等待队列。 如果线程池工作流程数>=corePoolSize并且等待队列已满,且工作线程数<maximumPoolSize,创建新线程执行task。 如果线程池工作流程数>=corePoolSize并且等待队列已满,且工作线程数=maximumPoolSize,执行拒绝策略。 execute()原理 public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task.The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 如果运行的线程数小于corePoolSize,尝试创建一个新线程(Worker),并执行 * 它的第一个任务command。 * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 如果task成功插入等待队列,我们仍需要进行双重校验是否可以成功添加一个线程 * (因为有的线程可能在我们上次检查以后已经死掉了)或者在我们进入这个 * 方法后线程池已经关闭了。 * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. * 如果等待队列已满,我们尝试新创建一个线程。如果创建失败,我们知道线程已关闭 或者已饱和,因此我们拒绝改任务。 */ int c = ctl.get(); // 高3位表示状态,低29位任务数量。 //工作线程小于核心线程数,创建新的线程。 if (workerCountOf(c) < corePoolSize) { //创建新的worker立即执行command,并且轮训workQueue处理task。 (核心线程数) if (addWorker(command, true)) return; c = ctl.get(); } //线程池在运行状态且可以将task插入队列 //第一次校验线程池在运行状态 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); //第二次校验,防止在第一次校验通过后线程池关闭。 如果线程池关闭,在队列中删除task并拒绝task if (! isRunning(recheck) && remove(command)) reject(command); //如果线程数=0(线程都死掉了,比如:corePoolSize=0),新建线程且未指 // 定firstTask,仅仅去轮训workQueue else if (workerCountOf(recheck) == 0) addWorker(null, false); } //线程队列已满,尝试创建新线程执行task,创建失败后拒绝task //创建失败原因:1.线程池关闭;2.线程数已经达到maxPoolSize else if (!addWorker(command, false)) reject(command); } addWorker()原理 线程管理部分的分水岭-方法调用之前 都是任务管理(任务的创建、以及拒绝、添加任务队列等,任务线程池的状态监控等),方法调用之后属于线程的管理。(线程的执行和阻塞等) 参数: firstTask:worker线程的初始任务,可以为空。 core:true:将corePoolSize作为上限,false:将maximumPoolSize作为上限 private boolean addWorker(Runnable firstTask, boolean core) { retry: //外层循环判断线程池的状态 for (;;) { //在进行获取一次上下文操作机制 int c = ctl.get(); int rs = runStateOf(c); //线程池状态 // Check if queue empty only if necessary. // 线程池状态:RUNNING = -1、SHUTDOWN = 0、 STOP = 1、TIDYING = 2、TERMINATED = 3 //线程池至少是shutdown状态 if (rs >= SHUTDOWN && // 除了线程池正在关闭(shutdown), // 队列里还有未处理的task的情况,其他都不能添加 ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; //内层循环判断是否到达容量上限,worker+1 for (;;) { int wc = workerCountOf(c);//worker数量 //worker大于Integer最大上限 //或到达边界上限 if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; //CAS worker+1 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 // CAS失败workerCount被其他线程改变, // 重新尝试内层循环CAS对workerCount+1 } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { final ReentrantLock mainLock = this.mainLock; w = new Worker(firstTask); //1.state置为-1,Worker继承了AbstractQueuedSynchronizer. //2.设置firstTask属性. //3.Worker实现了Runable接口,将this作为入参创建线程. final Thread t = w.thread; if (t != null) { //addWorker需要加锁 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int c = ctl.get(); int rs = runStateOf(c); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w);//workers是HashSet<Worker> //设置最大线程池大小 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; } 可以理解就是每一个Worker对象都是一个AQS队列哦! addWorker方法有4种传参的方式: addWorker(command, true) 线程数小于corePoolSize。判断workers(HashSet<Worker>)大小,如果worker数量>=corePoolSize返回false,否则创建worker添加到workers,并执行worker的run方法(执行firstTask并轮询tworkQueue); addWorker(command, false) 线程数大于corePoolSize且workQueue已满。如果worker数量>=maximumPoolSize返回false,否则创建worker添加到workers,并执行worker的run方法(执行firstTask并轮询tworkQueue); addWorker(null, false) 没有worker存活也就是任务梳理runcount为0,创建worker去轮询workQueue,长度限制maximumPoolSize。 addWorker(null, true) 在execute方法中就使用了前3种,结合这个核心方法进行以下分析 以上无论哪种方式都需要进行相关的ReentrantLock的加锁,所以效率和性能不会特别好。所以有了一个小办法,prestartAllCoreThreads() prestartAllCoreThreads()原理 这个方法调用,启动所有的核心线程去轮询workQueue。因为addWorker是需要上锁的,预启动核心线程可以提高执行效率。 ThreadPoolExecutor 内部类Worker (线程管理的核心类) /** * Class Worker mainly maintains interrupt control state for * threads running tasks, along with other minor bookkeeping. * This class opportunistically extends AbstractQueuedSynchronizer * to simplify acquiring and releasing a lock surrounding each * task execution. This protects against interrupts that are * intended to wake up a worker thread waiting for a task from * instead interrupting a task being run. We implement a simple * non-reentrant mutual exclusion lock rather than use * ReentrantLock because we do not want worker tasks to be able to * reacquire the lock when they invoke pool control methods like * setCorePoolSize. Additionally, to suppress interrupts until * the thread actually starts running tasks, we initialize lock * state to a negative value, and clear it upon start (in * runWorker). * 1.Worker类主要负责运行线程状态的控制。 * 2.Worker继承了AQS实现了简单的获取锁和释放所的操作。来避免中断等待执行任务的线 * 程时,中断正在运行中的线程(线程刚启动,还没开始执行任务)。 * 3.自己实现不可重入锁,是为了避免在实现线程池控状态控制的方法,例如: * setCorePoolSize的时候中断正在开始运行的线程。 * setCorePoolSize可能会调用interruptIdleWorkers(),该方法中会调用worker的tryLock()方法 * 中断线程,自己实现锁可以确保工作线程启动之前不会被中断 */ private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ 封装任务线程机制。 final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ Worker(Runnable firstTask) { // inhibit interrupts until runWorker //状态置为-1,如果中断线程需要CAS将state 从0- >1,以此来保证能只中断从workerQueue getTask的线程 setState(-1); this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } // 核心方法机制 /** Delegates main run loop to outer runWorker */ public void run() { //首先执行w.unlock,就是把state置为0,对该线程的中断就可以进行了 runWorker(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. protected boolean isHeldExclusively() { return getState() != 0; } // 在setCorePoolSize/shutdown等方法中断worker线程时需要调用该方法, // 确保中断的是从workerQueue getTask的线程 protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } //调用tryRelease修改state=0,LockSupport.unpark(thread) 下一个等待锁的线程 public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } } } 阻塞队列 workQueue 有多种选择,在 JDK 中一共提供了 7 中阻塞对列,分别为: ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。 此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平地访问队列 ,所谓公平访问队列是指阻塞的线程,可按照阻塞的先后顺序访问队列。非公平性是对先等待的线程是不公平的,当队列可用时,阻塞的线程都可以竞争访问队列的资格。 LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。 此队列的默认和最大长度为Integer.MAX_VALUE。 此队列按照先进先出的原则对元素进行排序。 PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。 (虽然此队列逻辑上是无界的,但是资源被耗尽时试图执行 add 操作也将失败,导致 OutOfMemoryError) DelayQueue: 一个使用优先级队列实现的无界阻塞队列。 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素 SynchronousQueue: 一个不存储元素的阻塞队列。 一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。(SynchronousQueue 该队列不保存元素) LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。 相对于其他阻塞队列LinkedTransferQueue多了tryTransfer和transfer方法。 LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。 是一个由链表结构组成的双向阻塞队列 关闭线程池 其实,如果优雅的关闭线程池是一个令人头疼的问题,线程开启是简单的,但是想要停止却不是那么容易的。通常而言, 大部分程序员都是使用 jdk 提供的两个方法来关闭线程池,他们分别是:shutdown 或 shutdownNow; 通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程(PS:中断,仅仅是给线程打上一个标记,并不是代表这个线程停止了,如果线程不响应中断,那么这个标记将毫无作用),所以无法响应中断的任务可能永远无法终止。 但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。 只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回 true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow 方法。 这里推荐使用稳妥的 shutdownNow 来关闭线程池,至于更优雅的方式我会在以后的并发编程设计模式中的两阶段终止模式中会再次详细介绍。 线程池的优点 在 Java 并发编程框架中的线程池是运用场景最多的技术,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来至少以下4个好处。 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗; 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行; 第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。 第四:提供更强大的功能,比如延时定时线程池; 线程池的场景特性 任务的性质:CPU密集型任务、IO密集型任务和混合型任务; 任务的优先级:高、中和低; 任务的执行时间:长、中和短; 任务的依赖性:是否依赖其他系统资源,如数据库连接; 性质不同的任务可以用不同规模的线程池分开处理。分为CPU密集型和IO密集型。 CPU密集型任务应配置尽可能小的线程,如配置 Ncpu+1个线程的线程池。 (可以通过Runtime.getRuntime().availableProcessors()来获取CPU物理核数),参考建议哈 IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu。 混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。 如果这两个任务执行时间相差太大,则没必要进行分解。 可以通过 Runtime.getRuntime().availableProcessors() 方法获得当前设备的CPU个数。 优先级不同的任务可以使用优先级队列 PriorityBlockingQueue来处理。它可以让优先级高的任务先执行(注意:如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行) 执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。 依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则 CPU 空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。 建议使用有界队列。有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点。方式因为提交的任务过多而导致 OOM;

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册