Struts2的缓存中最重要的两个类就是ReferenceMap与ReferenceCache。下面来解释下ReferenceCache中的get()方法。

public V get(final Object key) {
		V value = super.get(key);
		return (value == null) ? internalCreate((K) key) : value;
}

通过key来获取value操作首先调用了super.get(key)方法,也就是调用了ReferenceMap中变量delegate的get()方法,这个变量的定义如下:

transient ConcurrentMap<Object, Object> delegate;

支持并发的容量,所以不会有线程的问题。但是这个value值很可能为null,为什么呢?

(1)这个值本来就没有,当然为null

(2)key或value值存储的类型为软引用或弱引用,当JVM内存吃紧或回收内存时很可能让key/value对中的某个值为null,如果某个值为null,这个key/value对就会失效,会被Deman线程清理出ConcurrentMap容器中。  

如果为空,那么就走internalCreate()函数:

V internalCreate(K key) {
		try {
			FutureTask<V> futureTask = new FutureTask<V>(new CallableCreate(key));   // 1

			// use a reference so we get the same equality semantics.
			Object keyReference = referenceKey(key);   // 2
			// 有就返回futureTask,没有就返回null
			Future<V> future = futures.putIfAbsent(keyReference, futureTask); // 3
			if (future == null) {
				// winning thread.
				try {
					if (localFuture.get() != null) {  // 4
						// 不允许在同一个缓存内嵌套创建
						throw new IllegalStateException("Nested creations within the same cache are not allowed.");
					}

					localFuture.set(futureTask); // 5

					futureTask.run();            // 6
					V value = futureTask.get();  // 7
					putStrategy().execute(this, keyReference,referenceValue(keyReference, value));  // 8
					return value;
				} finally {
					localFuture.remove();    // 9
					futures.remove(keyReference); // 10
				}
			} else {
				// wait for winning thread.
				return future.get();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

  

(1)首先创建一个FutureTask对象,我们随后介绍。

(2)对key进行了一次封装引用(强引用、弱引用或者软引用),为什么要封装呢?主要还是想对弱引用与软引用的值进行比较。有机会我们再展开讨论这个问题。

(3)putIfAbsent()方法是个原子操作,也就是不存在就放入。futures变量定义如下:

transient ConcurrentMap<Object, Future<V>> futures = new ConcurrentHashMap<Object, Future<V>>();

当future为空时,进入了步骤4

(4)这里有个localFuture变量,定义如下:

transient ThreadLocal<Future<V>> localFuture = new ThreadLocal<Future<V>>();

这是个ThredLocal变量,本来就是线程安全的,但是localFuture.get()不为null时表示了嵌套创建的异常,这是什么原因呢?大家看第5步时为localFuture设置了值,而9处又移除了这个值。如果不为空,那说明线程设置了值在运行9之前的代码时又重新进入了这个if判断中。

到底是什么操作让这个线程又重要进入了if判断中呢?接着往下看。

(6)这里运行了FutureTask任务,也就是CallableCreate类的call()方法,然后线程在7处阻塞等待结果。另外一个线程运行call()方法来生成结果。

class CallableCreate implements Callable<V> {
		K key;
		public CallableCreate(K key) {
			this.key = key;
		}
		public V call() {
			// try one more time (a previous future could have come and gone.)
			// 这里又对值进行了一次判定  从缓存中查找值
			V value = internalGet(key);
			if (value != null) {
				return value;
			}
			// create value.
			value = create(key); // 自定义加载数据的方式
			if (value == null) {
				throw new NullPointerException("create(K) returned null for: " + key);
			}
			return value;
		}
	}

在call()方法中再次调用了intrnalGet(key)来判断缓存中是否有这个值,我们记得在之前的线程中也做过这个判断,就是调用get()方法中的super.get()方法的时候。为什么还要重新判断一下呢?

现在假设有C线程在A线程执行if判断语句中的代码时也用相同的key调用了get()方法,但是A线程已经得到B线程的结果,执行完了10处理的代码,而这里的C线程恰好已经走过了get()方法中的super.get()方法,

导致没有获取到结果,在走if判断后调用了futureTask来重新获取结果,这时的call()方法中的internalGet()判断可就起作用了,直接取出B线程运行好的被A线程放入Map中。

还有一种情况,如果C线程在A线程还没有运行得到结果(在7处阻塞着呢)那么就会走else中的代码,因为A线程已经将futureTask放到Map中了。C线程也被要求阻塞等待B线程的结果。

如果缓存中没有则只能调用create()方法创建了,在ReferenceCache类中create()方法定义如下:

	protected abstract V create(K key);

这是一个抽像方法,Struts2中有一个具体的使用来看一下:

final Map<Class<?>, List<Injector>> injectors = new ReferenceCache<Class<?>, List<Injector>>() {

		protected List<Injector> create(Class<?> key) {
			List<Injector> injectors = new ArrayList<Injector>();
			addInjectors(key, injectors);
			return injectors;
		}

};

可以看到覆写了create()方法,所以call()方法就会调用上面的create()方法来创建值。 

现在来想想4处的嵌套创建代码,如果这里的create()调用了get()方法,并且传入相同的key值,会出现什么结果呢?

剑指架构师系列-Struts2的缓存的更多相关文章

  1. 剑指架构师系列-Struts2构造函数的循环依赖注入

    Struts2可以完成构造函数的循环依赖注入,来看看Struts2的大师们是怎么做到的吧! 首先定义IBlood与BloodImpl类: public interface IBlood { } pub ...

  2. 剑指架构师系列-InnoDB存储引擎、Spring事务与缓存

    事务与锁是不同的.事务具有ACID属性: 原子性:持久性:由redo log重做日志来保证事务的原子性和持久性,一致性:undo log用来保证事务的一致性隔离性:一个事务在操作过程中看到了其他事务的 ...

  3. 剑指架构师系列-Linux下的调优

    1.I/O调优 CentOS下的iostat命令输出如下: $iostat -d -k 1 2 # 查看TPS和吞吐量 参数 -d 表示,显示设备(磁盘)使用状态:-k某些使用block为单位的列强制 ...

  4. 剑指架构师系列-spring boot的logback日志记录

    Spring Boot集成了Logback日志系统. Logback的核心对象主要有3个:Logger.Appender.Layout 1.Logback Logger:日志的记录器 主要用于存放日志 ...

  5. 剑指架构师系列-持续集成之Maven+Nexus+Jenkins+git+Spring boot

    1.Nexus与Maven 先说一下这个Maven是什么呢?大家都知道,Java社区发展的非常强大,封装各种功能的Jar包满天飞,那么如何才能方便的引入我们项目,为我所用呢?答案就是Maven,只需要 ...

  6. 剑指架构师系列-tomcat6通过IO复用实现connector

    由于tomcat6的配置文件如下: <Connector port="80" protocol="org.apache.coyote.http11.Http11Ni ...

  7. 剑指架构师系列-tomcat6通过伪异步实现connector

    首先在StandardService中start接收请求的线程,如下: synchronized (connectors) { for (int i = 0; i < connectors.le ...

  8. 剑指架构师系列-Hibernate需要掌握的Annotation

    1.一对多的关系配置 @Entity @Table(name = "t_order") public class Order { @Id @GeneratedValue priva ...

  9. 剑指架构师系列-MySQL调优

    介绍MySQL的调优手段,主要包括慢日志查询分析与Explain查询分析SQL执行计划 1.MySQL优化 1.慢日志查询分析 首先需要对慢日志进行一些设置,如下: SHOW VARIABLES LI ...

随机推荐

  1. C#程序中注释过多的8条理由

    程序中中的注释,一般是有益处的,可以知晓程序的一些逻辑说明,或是参数解释.但是有些程序,因为注释太多,反而引起维护上的不方便,删掉了怕以后不能出现问题不好查找原因,不删除留在代码中,对程序的维护人员, ...

  2. android: 多线程编程基础

    9.1   服务是什么 服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那 些不需要和用户交互而且还要求长期运行的任务.服务的运行不依赖于任何用户界面,即使 ...

  3. Bitbucket Repository size limits

    Repository size limits By Justen Stepka, Product Manager on May 30, 2014 In order to improve and mai ...

  4. ORA-00257: archiver error. Connect internal only, until freed——解决

    参考http://www.2cto.com/database/201109/104615.html, 开启归档后,操作一个大表迁移表空间,执行了1个多小时没完成就手动给中断了,但是再次用plsql登陆 ...

  5. cocos2d-x.0创建工程

    $ python create-multi-platform-projects.py -p PompaDroid -k cn.philon.pompadroid -l cpp

  6. Activemq消息持久化

    官方文档: http://activemq.apache.org/persistence.html ActiveMq持久化相关配置:/usr/local/apache-activemq-5.11.1/ ...

  7. Android学习之AsyncTask

    我们在<Android学习之Handler消息传递机制>(http://www.cnblogs.com/zhouhb/p/5812447.html)已提到过,Android只允许UI线程修 ...

  8. 一个winform带你玩转rabbitMQ

    源码已放出 https://github.com/dubing/MaoyaRabbit 本章分3部分 一.安装部署初探 二.进阶 三.api相关 安装 部署 初探 先上图 一. 安装部署 下载 rab ...

  9. Java对象生命周期

    [TOC] 1. 创建阶段(Created) 为对象分配存储空间 开始构造对象 从父类到子类对static成员进行初始化 父类成员变量按照顺序初始化,递归调用父类的构造方法 子类成员变量按照顺序初始化 ...

  10. 一些常用的Git命令

    1 删除文件 $ git rm filename 从已跟踪文件清单中移除,并连带从工作目录中删除指定的文件. 删除后,有2种选择: (1)如果确实要删除,则使用git commit提交. (2)如果不 ...