剑指架构师系列-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值,会出现什么结果呢?
剑指架构师系列-Struts2的缓存的更多相关文章
- 剑指架构师系列-Struts2构造函数的循环依赖注入
Struts2可以完成构造函数的循环依赖注入,来看看Struts2的大师们是怎么做到的吧! 首先定义IBlood与BloodImpl类: public interface IBlood { } pub ...
- 剑指架构师系列-InnoDB存储引擎、Spring事务与缓存
事务与锁是不同的.事务具有ACID属性: 原子性:持久性:由redo log重做日志来保证事务的原子性和持久性,一致性:undo log用来保证事务的一致性隔离性:一个事务在操作过程中看到了其他事务的 ...
- 剑指架构师系列-Linux下的调优
1.I/O调优 CentOS下的iostat命令输出如下: $iostat -d -k 1 2 # 查看TPS和吞吐量 参数 -d 表示,显示设备(磁盘)使用状态:-k某些使用block为单位的列强制 ...
- 剑指架构师系列-spring boot的logback日志记录
Spring Boot集成了Logback日志系统. Logback的核心对象主要有3个:Logger.Appender.Layout 1.Logback Logger:日志的记录器 主要用于存放日志 ...
- 剑指架构师系列-持续集成之Maven+Nexus+Jenkins+git+Spring boot
1.Nexus与Maven 先说一下这个Maven是什么呢?大家都知道,Java社区发展的非常强大,封装各种功能的Jar包满天飞,那么如何才能方便的引入我们项目,为我所用呢?答案就是Maven,只需要 ...
- 剑指架构师系列-tomcat6通过IO复用实现connector
由于tomcat6的配置文件如下: <Connector port="80" protocol="org.apache.coyote.http11.Http11Ni ...
- 剑指架构师系列-tomcat6通过伪异步实现connector
首先在StandardService中start接收请求的线程,如下: synchronized (connectors) { for (int i = 0; i < connectors.le ...
- 剑指架构师系列-Hibernate需要掌握的Annotation
1.一对多的关系配置 @Entity @Table(name = "t_order") public class Order { @Id @GeneratedValue priva ...
- 剑指架构师系列-MySQL调优
介绍MySQL的调优手段,主要包括慢日志查询分析与Explain查询分析SQL执行计划 1.MySQL优化 1.慢日志查询分析 首先需要对慢日志进行一些设置,如下: SHOW VARIABLES LI ...
随机推荐
- 微软BI 之SSIS 系列 - 通过设置 CheckPoints 检查点来增强 SSIS Package 流程的重用性
开篇介绍 通常一个 ETL Package 是由多个控制流和数据流共同组成,有的时候 ETL 的步骤可能会比较多,整个流程执行下来的时间可能比较长.假设在 ETL Package 中包含5个Task, ...
- 转:LAV Filter 源代码分析
1: 总体结构 LAV Filter 是一款视频分离和解码软件,他的分离器封装了FFMPEG中的libavformat,解码器则封装了FFMPEG中的libavcodec.它支持十分广泛的视音频格式. ...
- C# 动态修改dll的签名 以及修改引用该dll文件的签名
在读取RedisSessionStateProvider配置 提到用mono ceil 来修改程序集以及它的签名,里面GetPublicKey 和GetPubliKeyToken 方法里面那个字符串的 ...
- 漫谈Puppet4
激动人心的改进 速度,速度,还是速度 稳定性和鲁棒性的提升 全新的Parser “不变"的agent 不兼容的改动 包管理方式的变化 配置文件/目录的路径变化 其他路径变化 Director ...
- raid性能对比
1,raid0的特性:采用剥离,数据将在几个磁盘上进行分割.数据被分成很多数据块,每一数据块会被写入不同的磁盘.从而, 每一磁盘的工作负荷都得到了降低,这有助于加速数据传输.RAID-0可让磁盘更好地 ...
- Apache Storm 与 Spark:对实时处理数据,如何选择【翻译】
原文地址 实时商务智能这一构想早已算不得什么新生事物(早在2006年维基百科中就出现了关于这一概念的页面).然而尽管人们多年来一直在对此类方案进行探讨,我却发现很多企业实际上尚未就此规划出明确发展思路 ...
- 【C++沉思录】句柄2
1.[C++沉思录]句柄1 存在问题: 句柄为了绑定到Point的对象上,必须定义一个辅助类UPoint,如果要求句柄绑定到Point的子类上,那就存在问题了.2.有没有更简单的办法呢? 句柄使用Po ...
- JS 获取自定义标签
<abc-aaa xwe='sdf'>AAAAAAAAAAAAAAAAAAAAAA</abc-aaa> alert($("abc-aaa").attr(&q ...
- WinDbg使用介绍
Windbg工作空间 WinDbg使用工作空间来描述和存储调试项目的属性.参数及调试器设置等信息.工作空间与vc中的项目文件很相似.WinDbg定义了两种工作空间,一种为默认工作空间,另一种为命名的工 ...
- ECshop导入淘宝数据包乱码问题解决方法
ECshop在导入淘宝数据包的时候出现数据乱码. 测试版本 ecshop2.73 利用淘宝助手导出一个数据包(.csv),不要一次全部商品导出,最好是将数据包控制在1M左右,因为ecshop对上传文件 ...