`
zddava
  • 浏览: 243621 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

oscache源代码阅读(三) -- 基本缓存实现

阅读更多
oscache的默认缓存实现是由4个类组成的,如下图所示:



首先来看一下是如何放入缓存的操作吧,也就是AbstractConcurrentReadCache类的#put()方法:

	public Object put(Object key, Object value) {
		return put(key, value, true);
	}

	// 这里的第三个参数代表是否持久化缓存
	private Object put(Object key, Object value, boolean persist) {
		if (value == null) {// 默认是不支持空值的
			throw new NullPointerException();
		}

		// 计算hash
		int hash = hash(key);
		// hash表,其实Entry本身是一个链表的结构,也就是hash桶
		Entry[] tab = table;

		// 将hash值与hash表的长度按位与得到初始位置
		int index = hash & (tab.length - 1);

		// first指的是hash表中Entry链表的第一个元素
		Entry first = tab[index];
		Entry e = first;

		for (;;) {
			if (e == null) {// 如果哈希表当前位置是空位
				synchronized (this) {
					tab = table;

					Object oldValue = null;

					// Remove an item if the cache is full
					if (size() >= maxEntries) {// 如果缓存已满,需要挑选一个Entry移出
						// part of fix CACHE-255: method should return old value
						// 挑选要移出的key的方法#removeItem()是由之类去实现的
						oldValue = remove(removeItem(), false, false);
					}

					if (first == tab[index]) {// 这里是对可能存在的并发更新的处理
						// Add to front of list
						Entry newEntry = null;

						if (memoryCaching) {
							newEntry = new Entry(hash, key, value, first);
						} else {
							newEntry = new Entry(hash, key, NULL, first);
						}

						tab[index] = newEntry;

						// 通知具体实现值已经放入缓存的回调
						itemPut(key);

						// Persist if required
						// 这里如果配置文件中cache.memory=false并且cache.persistence.overflow.only=true程序就进入了一个混乱的状态了
						// 因为内存中的Entry值为NULL,并且不会调用持久化存储
						// 所以这两个配置项配合的话只有3种情况了
						// (1) memory=true, overflow=true:使用内存缓存,溢出的数据持久化
						// (1) memory=true, overflow=false:使用内存缓存,溢出的数据不处理
						// (1) memory=false, overflow=false:使用持久化缓存

						if (persist && !overflowPersistence) {// 如果需要持久化保存
							persistStore(key, value);
						}

						// If we have a CacheEntry, update the group lookups
						if (value instanceof CacheEntry) {
							// 更新缓存的分组信息,其实AbstractConcurrentReadCache
							// 用一个HashMap保存了分组名和各个key之间的一个映射 groupname -> Set<Key>
							updateGroups(null, (CacheEntry) value, persist);
						}

						// 如果数量大于threshold(capacity * 装填因子(loadfactor))
						if (++count >= threshold) {// 是否rehash
							rehash();
						} else {
							recordModification(newEntry);
						}

						return oldValue;
					} else {
						// 如果当前hash表发生了变化,即发生了并发插入缓存的操作,此时需要进入这个分支
						// #sput()里边的逻辑和#put()是类似的
						return sput(key, value, hash, persist);
					}
				}
			} else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {// 如果当前的key已经存在了,更新值
				// synch to avoid race with remove and to
				// ensure proper serialization of multiple replaces
				synchronized (this) {
					tab = table;

					Object oldValue = e.value;

					// [CACHE-118] - get the old cache entry even if there's no
					// memory cache
					// oldValue为NULL代表了是磁盘缓存
					if (persist && (oldValue == NULL)) {
						// 在磁盘里去的缓存值
						oldValue = persistRetrieve(key);
					}

					if ((first == tab[index]) && (oldValue != null)) {
						if (memoryCaching) {
							// 缓存更新值
							e.value = value;
						}

						// Persist if required
						if (persist && overflowPersistence) {
							// 如果缓存溢出需要持久化,在缓存持久化处移除这个值
							// 因为现在内存中已经有这个值了,不能再持久化了
							// 这里因为是更新,所以按理说不会有它对应的overflow缓存的啊?
							persistRemove(key);
						} else if (persist) {// 持久化保存
							persistStore(key, value);
						}

						updateGroups(oldValue, value, persist);
						itemPut(key);

						return oldValue;
					} else {
						return sput(key, value, hash, persist);
					}
				}
			} else {// 将e指向Entry链表的下一个项目
				e = e.next;
			}
		}
	}


整个的流程用代码的注释其实就可以写清楚了,注意,在更新缓存后会调用给之类的回调函数#itemPut(),另外还有参数cache.memory和cache.persistence.overflow.only对流程的影响。

下面看下#get(),这里#remove()就不写了其实过程反倒和#get()也差不多:

	public Object get(Object key) {
		if (log.isDebugEnabled()) {
			log.debug("get called (key=" + key + ")");
		}

		// 计算hash
		int hash = hash(key);

		/*
		 * Start off at the apparently correct bin. If entry is found, we need
		 * to check after a barrier anyway. If not found, we need a barrier to
		 * check if we are actually in right bin. So either way, we encounter
		 * only one barrier unless we need to retry. And we only need to fully
		 * synchronize if there have been concurrent modifications.
		 */
		// 计算在hash表中的位置
		Entry[] tab = table;
		int index = hash & (tab.length - 1);
		// Entry链表中的第一个数据
		Entry first = tab[index];
		Entry e = first;

		for (;;) {
			if (e == null) {
				// If key apparently not there, check to
				// make sure this was a valid read
				// key没找到,再次查看hash表确定是否真的找不到了
				tab = getTableForReading();

				if (first == tab[index]) {
					// Not in the table, try persistence
					// 试着在持久化处找
					Object value = persistRetrieve(key);

					if (value != null) {
						// Update the map, but don't persist the data
						// 在持久化处找到数据的话需要更新hash表,但不去重新持久化
						put(key, value, false);
					}

					return value;
				} else {
					// Wrong list -- must restart traversal at new first
					e = first = tab[index = hash & (tab.length - 1)];
				}
			}
			// checking for pointer equality first wins in most applications
			else if ((key == e.key) || ((e.hash == hash) && key.equals(e.key))) {// 找到了数据
				Object value = e.value;

				if (value != null) {
					if (NULL.equals(value)) {
						// Memory cache disable, use disk
						// 需要去缓存找数据
						value = persistRetrieve(e.key);

						if (value != null) {
							// 调用回调
							itemRetrieved(key);
						}

						return value; // fix [CACHE-13]
					} else {
						// 调用回调
						itemRetrieved(key);

						return value;
					}
				}

				// Entry was invalidated during deletion. But it could
				// have been re-inserted, so we must retraverse.
				// To avoid useless contention, get lock to wait out
				// modifications
				// before retraversing.
				synchronized (this) {
					tab = table;
				}

				// 到这里其实是列表处于一个错误的状态了,重新循环
				e = first = tab[index = hash & (tab.length - 1)];
			} else {// 需要查看链表中的下一个元素
				e = e.next;
			}
		}
	}


其实这个#get()在一些并发控制的精妙上我也看不出来,只能留待以后水平高了的时候去研究了,现在能看懂的也只有大致的流程。

最后对3个默认缓存实现中的LRU进行下简单的分析,实现方法挺简单的,不过有一些借鉴意义。

首先,LRUCache使用LinkedHashSet来将key值进行是否最近使用的排序,越往后越是最近使用的Key

    private Collection list = new LinkedHashSet();

前面不是说在put,get,remove中都有之类的回调函数吗,这里就派上用场了

	protected void itemRetrieved(Object key) {
		while (removeInProgress) {
			try {
				Thread.sleep(5);
			} catch (InterruptedException ie) {
			}
		}

		// 这里改变了list中元素的顺序
		synchronized (list) {
			list.remove(key);
			list.add(key);
		}
	}

	protected void itemPut(Object key) {
		// 这里改变了list中元素的顺序
		synchronized (list) {
			list.remove(key);
			list.add(key);
		}
	}


这样,在缓存已满需要查找待移除的Key时,就可以使用list的顺序了,很简单,但是很有效。

	private Object removeFirst() {
		Object toRemove = null;

		synchronized (list) { // A further fix for CACHE-44 and CACHE-246
			Iterator it = list.iterator();
			toRemove = it.next();
			it.remove();
		}

		return toRemove;
	}
  • 大小: 7.2 KB
1
1
分享到:
评论

相关推荐

    oscache缓存技术应用

    - 将`oscache.properties`和`oscache.tld`放入`WEB-INF/classes`(或自动编译到此目录的源代码目录)。 - 配置`oscache.properties`以满足项目需求。 2. **使用方法** - **缓存对象**:通过调用API接口直接缓存...

    oscache-2.4.1-full

    - `src`:源代码目录,对于开发者来说,可以查看源代码以便理解和学习OSCache的工作原理。 - `lib`:依赖的第三方库,OSCache可能需要这些库来正常工作。 - `etc`:通常包含配置文件,如`oscache.properties`,...

    OsCache缓存框架使用示例

    OsCache是Java应用程序中常用的缓存框架,它能够有效地提高应用程序的性能,通过将经常访问的数据存储在内存中,减少对数据库或其他数据源的访问,从而降低系统负载。本示例将通过一个天气预报Web服务的场景,详细...

    oscache-JSP缓存

    - 编写Java代码:在Servlet或Controller中,使用osCache API进行缓存操作。 **5. 示例应用** 以下是一个简单的osCache在JSP页面中的应用示例: ```jsp &lt;%@ taglib uri="http://www.opensymphony.com/oscache" ...

    一个OSCache缓存技术的关键zip包

    - src:源代码目录,对于开发者来说,可以查看OSCache的实现细节,学习其设计模式或者进行二次开发。 - lib:可能包含OSCache依赖的其他库文件,如log4j等,确保OSCache的正常运行。 3. 使用OSCache的步骤: - ...

    oscache-2.2jar包

    5. **src**:源代码目录,如果提供的话,可以查看osCache的内部实现,这对于学习和调试很有帮助。开发者可以研究源码来理解osCache的工作原理,并且在必要时进行定制或扩展。 6. **lib**:依赖库目录,可能包含了...

    oscache-2.1.1-full.zip_full_oscache_oscache 2_oscache2

    2. **src**: 源代码目录,提供了osCache的全部源代码,便于开发者深入研究和定制。 3. **www.pudn.com.txt**: 可能是下载来源的注释或说明文件。 4. **lib**: 库文件夹,包含osCache运行所依赖的JAR文件和其他第三...

    java缓存_源代码

    在给定的资源中,我们可以看到四个项目源代码,分别与缓存相关的技术有关,可能是为了演示或实现不同的缓存策略。此外,还有一个"java缓存_other"目录,其中可能包含与这些缓存系统相关的文档,如设计文档、使用手册...

    oscache 资料文件

    Cache是一种用于提高系统响应速度、改善系统运行性能的技术。尤其是在Web应用中,通过缓存页面的输出结果,可以很显著的改善...本文中作者给大家介绍一个实现J2EE框架中Web应用层缓存功能的开放源代码项目----OSCache。

    oscache缓存

    `src`目录可能包含了源代码,对于学习osCache的工作原理和进行定制化开发非常有用。`lib`目录可能包含了osCache运行所依赖的其他库文件,确保其正常运行。`etc`目录通常包含配置文件,如`oscache.properties`,用于...

    OSCACHE配置URL实现页面缓存的Fliter(修改配置无需重启)

    描述中提到的“源代码”和“博文链接”暗示我们将通过阅读源码和博客文章来理解如何配置OSCache Filter以实现URL级别的缓存,并且这种配置变动在不重启服务器的情况下即可生效。 **OSCache基础** 1. **OSCache简介...

    oscache的例子

    OSCache标记库由OpenSymphony设计,它是一种开创性的缓存方案,它提供了在现有JSP页面之内实现内存缓存的功能。OSCache是个一个被广泛采用的高性能的J2EE缓存框架,OSCache还能应用于任何Java应用程序的普通的缓存...

    oscache说明

    通常,这两个文件会放在项目的源代码目录(如 `src`),在构建过程中会被自动复制到 `WEB-INF/classes`。 3. 根据你的应用需求,配置 `oscache.properties` 文件以设置缓存参数。 **使用方法** OSCache 提供了多种...

    oscache 例子

    `osCache`是一个开源的Java缓存框架,主要用于提高应用程序的性能和响应速度,通过将经常访问的数据存储在内存中,避免频繁的数据库查询。它最初由OpenSymphony团队开发,现在已被Apache软件基金会的Tapestry项目所...

    Oscache使用教程

    2. **初始化配置**:在Java代码中,通过`org.oscache.core.CacheManager`初始化Oscache。可以设置缓存的大小、过期策略、序列化方式等。例如: ```java CacheManager cacheManager = CacheManager.create(); ...

    当前最流行的缓存技术_demo

    当前最流行的缓存技术_demo ...本文中作者给大家介绍一个实现J2EE框架中Web应用层缓存功能的开放源代码项目----OSCache。通过应用OSCache,我们不但可以实现通常的Cache功能,还能够改善系统的稳定性。

    oscache文档

    `docs` 目录包含文档,`etc` 包含配置文件,`lib` 存放依赖库,而 `src` 则包含源代码。 配置 OSCache 时,将 `oscache.properties` 和 `oscache.tld` 以及 `oscache-2.1.1.jar` 复制到你的应用服务器的相应目录下...

    oscache2.1_ful

    5. `src`: 源代码,便于学习和自定义扩展。 通过理解和使用 OSCache,开发者可以有效优化 J2EE 应用的性能,提升用户体验。对于处理高并发、大数据量的 Web 应用来说,OSCache 是一个值得考虑的缓存解决方案。

    OSCache 资料

    压缩包中的`readme.txt`通常包含了安装和使用指南,`etc`目录可能包含配置文件示例,`docs`目录下可能有更详细的文档和API参考,`src`包含源代码供学习和调试,而`lib`目录则可能包含了OSCache运行所需的其他库文件...

Global site tag (gtag.js) - Google Analytics