剑指架构师系列-Struts2的缓存

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值,会出现什么结果呢?

时间: 2024-10-13 21:44:21

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

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

Struts2可以完成构造函数的循环依赖注入,来看看Struts2的大师们是怎么做到的吧! 首先定义IBlood与BloodImpl类: public interface IBlood { } public class BloodImpl implements IBlood{ private IPeople people; @Inject public BloodImpl(@Inject IPeople people) { System.out.println("Blood 构造函数被调用.&q

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

事务与锁是不同的.事务具有ACID属性: 原子性:持久性:由redo log重做日志来保证事务的原子性和持久性,一致性:undo log用来保证事务的一致性隔离性:一个事务在操作过程中看到了其他事务的结果,如幻读.锁是用于解决隔离性的一种机制.事务的隔离级别通过锁的机制来实现. 数据库的事务隔离级别有(多个事务并发的情况下): 1.read uncommitted #首先,修改隔离级别 set tx_isolation='READ-UNCOMMITTED'; select @@tx_isolat

剑指架构师系列-设计模式

1.单例模式: 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 单例模式有以下几个要素: 私有的构造方法 指向自己实例的私有静态引用 以自己实例为返回值的静态的公有的方法 单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例.饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用:而懒汉式在调用取得实例方法的时候才会实例化对象. 饿汉式: public class Singleton_Simple { private static final Si

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

1.I/O调优 CentOS下的iostat命令输出如下: $iostat -d -k 1 2 # 查看TPS和吞吐量 参数 -d 表示,显示设备(磁盘)使用状态:-k某些使用block为单位的列强制使用Kilobytes为单位:1 10表示,数据显示每隔1秒刷新一次,共显示2次. tps:该设备每秒的传输次数,也就是一次I/O请求.多个逻辑请求可能会被合并为"一次I/O请求"."一次传输"请求的大小是未知的. kB_read/s:每秒从设备读取的数据量:kB_wr

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

1.Nexus与Maven 先说一下这个Maven是什么呢?大家都知道,Java社区发展的非常强大,封装各种功能的Jar包满天飞,那么如何才能方便的引入我们项目,为我所用呢?答案就是Maven,只需要粘贴个Jar包的地址,Maven就会自动到网上查找引入到你的项目中.不过首先你的下载个Maven,然后指定一下 当下来的包包(jar)放到哪里. 我的版本是apache-maven-3.2.1,找到conf里面的配置文件 settings.xml,瞅瞅有没有 <localRepository>E:

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

Spring Boot集成了Logback日志系统. Logback的核心对象主要有3个:Logger.Appender.Layout 1.Logback Logger:日志的记录器 主要用于存放日志对象,也可以定义日志类型.级别. 级别:ERROR.WARE.INFO.DEBUG和TRACE.没有FATAL,归纳到了ERROR级别里.ERROR.WARN and INFO level messages are logged by default. 在Spring Boot中,最好定义为logb

剑指架构师系列-MySQL的安装及主从同步

1.安装数据库 wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum install mysql-community-server 安装时使用root用户权限.安装成功后即可进行启动: /bin/systemctl restart mysqld.service 修改MySQL数据库root用户的密码,如

剑指架构师系列-Redis集群部署

初步搭建Redis集群 克隆已经安装Redis的虚拟机,我们使用这两个虚拟机中的Redis来搭建集群. master:192.168.2.129 端口:7001 slave:192.168.2.132 端口:7002 sentinel:192.168.2.129 端口:26379 来说一下这个sentinel,sentinel是一个管理redis实例的工具,它可以实现对redis的监控.通知.自动故障转移.sentinel不断的检测redis实例是否可以正常工作,通过API向其他程序报告redi

剑指架构师系列-持续集成之Maven实现项目的编译、发布和部署

Maven组织项目进行编译.部署 Maven项目基本的结构说明如下: mazhi  // 控制所有荐的编译.部署.发布 mazhi-app-parent  // 项目的父项目,有一些公共的设置可以被子项目继承 mazhi-core  // 基础服务项目,例如公共类等 mazhi-xxx 其中mazhi和mazhi-app-parent是pom格式,而mazhi-core是jar格式,还可以是 war等格式. 我们以新建mazhi和mazhi-core项目为例说明一下. 新建Maven-proje