oscache源代码阅读(一) -- 将pojo放入缓存

其实之前我完全没有接触过oscache,今天突发奇想,准备看看缓存是怎么实现的,google了一下,决定看看oscache的源码,简单的写了个TestCase:

	@Test
	public void testPojoCache() throws Exception {
		TestPojo pojo = new TestPojo("0001");
		pojo.setField1(100);
		pojo.setField2("100");

		Properties prop = new Properties();
		InputStream is = this.getClass().getResourceAsStream("oscache.properties");
		prop.load(is);

		is.close();

		GeneralCacheAdministrator cacheAdmin = new GeneralCacheAdministrator(prop);

		cacheAdmin.putInCache(pojo.getId(), pojo);

		TestPojo cachedObj = (TestPojo) cacheAdmin.getFromCache("0001");

		assertEquals(100, cachedObj.getField1());
		assertEquals("100", cachedObj.getField2());

	}

所以我对这个产品的熟悉程度基本为0,各位大虾看了后发现误人子弟请用砖轻拍。

简单了看了下oscache的介绍,发现它支持三种缓存方式:JSP Caching、Request Caching、General-Purpose Cache,今天的文章就是针对最后一种General-Purpose Cache的,在oscache中对应的入口是GeneralCacheAdministrator,就从这里开始吧:

	public GeneralCacheAdministrator(Properties p) {
		super(p);
		log.info("Constructed GeneralCacheAdministrator()");
		createCache();
	}

	// 这个是super(p)调用的构造函数
	protected AbstractCacheAdministrator(Properties p) {
		loadProps(p);
		initCacheParameters();

		if (log.isDebugEnabled()) {
			log.debug("Constructed AbstractCacheAdministrator()");
		}
	}

	// 初始化参数
	private void initCacheParameters() {
		algorithmClass = getProperty(CACHE_ALGORITHM_KEY);

		blocking = "true".equalsIgnoreCase(getProperty(CACHE_BLOCKING_KEY));

		String cacheMemoryStr = getProperty(CACHE_MEMORY_KEY);

		if ((cacheMemoryStr != null) && cacheMemoryStr.equalsIgnoreCase("false")) {
			memoryCaching = false;
		}

		unlimitedDiskCache = Boolean.valueOf(config.getProperty(CACHE_DISK_UNLIMITED_KEY)).booleanValue();
		overflowPersistence = Boolean.valueOf(config.getProperty(CACHE_PERSISTENCE_OVERFLOW_KEY))
				.booleanValue();

		String cacheSize = getProperty(CACHE_CAPACITY_KEY);

		try {
			if ((cacheSize != null) && (cacheSize.length() > 0)) {
				cacheCapacity = Integer.parseInt(cacheSize);
			}
		} catch (NumberFormatException e) {
			log.error("The value supplied for the cache capacity, '" + cacheSize
					+ "', is not a valid number. The cache capacity setting is being ignored.");
		}
	}

	// 创建缓存实例
	private void createCache() {
		log.info("Creating new cache");

		// 这里构建缓存时用到的参数都是在父类里的#initCacheParameters()中初始化的
		applicationCache = new Cache(isMemoryCaching(), isUnlimitedDiskCache(), isOverflowPersistence(),
				isBlocking(), algorithmClass, cacheCapacity);

		configureStandardListeners(applicationCache);
	}

	// Cache的构造函数
	public Cache(boolean useMemoryCaching, boolean unlimitedDiskCache, boolean overflowPersistence,
			boolean blocking, String algorithmClass, int capacity) {
		// 这里说明还是支持自定义的缓存策略的
		if (((algorithmClass != null) && (algorithmClass.length() > 0)) && (capacity > 0)) {
			try {
				cacheMap = (AbstractConcurrentReadCache) Class.forName(algorithmClass).newInstance();
				cacheMap.setMaxEntries(capacity);
			} catch (Exception e) {
				log.error("Invalid class name for cache algorithm class. " + e.toString());
			}
		}

		if (cacheMap == null) {
			// 选择一个默认的策略
			if (capacity > 0) {// 如果有缓存数目的限制,使用LRU
				cacheMap = new LRUCache(capacity);
			} else {// 否则使用Unlimited
				cacheMap = new UnlimitedCache();
			}
		}

		cacheMap.setUnlimitedDiskCache(unlimitedDiskCache);
		cacheMap.setOverflowPersistence(overflowPersistence);
		cacheMap.setMemoryCaching(useMemoryCaching);

		this.blocking = blocking;
	}

	// 配置事件的listener
	protected Cache configureStandardListeners(Cache cache) {
		if (config.getProperty(PERSISTENCE_CLASS_KEY) != null) {
			cache = setPersistenceListener(cache);
		}

		if (config.getProperty(CACHE_ENTRY_EVENT_LISTENERS_KEY) != null) {
			// Grab all the specified listeners and add them to the cache's
			// listener list. Note that listeners that implement more than
			// one of the event interfaces will be added multiple times.
			CacheEventListener[] listeners = getCacheEventListeners();

			for (int i = 0; i < listeners.length; i++) {
				// Pass through the configuration to those listeners that
				// require it
				if (listeners[i] instanceof LifecycleAware) {
					try {
						((LifecycleAware) listeners[i]).initialize(cache, config);
					} catch (InitializationException e) {
						log.error("Could not initialize listener '" + listeners[i].getClass().getName()
								+ "'. Listener ignored.", e);

						continue;
					}
				}

				if (listeners[i] instanceof CacheEntryEventListener) {
					cache.addCacheEventListener(listeners[i]);
				} else if (listeners[i] instanceof CacheMapAccessEventListener) {
					cache.addCacheEventListener(listeners[i]);
				}
			}
		}

		return cache;
	}


这里对缓存#initCacheParameters()和#configureStandardListeners()里的参数大致了解下,代码里有注释良好的JavaDoc,很容易看懂。

1. cache.algorithm 缓存的策略,具体就是用哪种Map来实现缓存,默认有FIFO,LRU,Unlimited三种,也支持自定义的缓存类型的;
2. cache.blocking 是否等待新数据放入缓存,这个感觉应该是在并发读取数据的时候如果数据在其他线程正处于写入的状态这个线程会wait()直到写入线程完成写入后notifyAll();
3. cache.memory 这个是是否使用内存缓存,这个多数情况下都应该是内存的吧;
4. cache.unlimited.disk 看介绍写的是当对象需要持久化缓存(应该是串行化吧)时,是否使用无限的磁盘空间;
5. cache.persistence.overflow.only 这个说明持久化缓存是不是仅在溢出的方式下开启,字面理解应该是否仅在是memory缓存不足的情况开启持久化缓存;
6. cache.capacity 保存的对象的数目。
7. cache.persistence.class 用于持久化的类名
8. cache.event.listeners 缓存事件监听器,多个listener使用逗号分隔开

初始化大致就这么多,还是看看GeneralCacheAdministrator#putInCache()方法吧,这里#putInCache()有4个,我挑了一个参数最多的:putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy)

	/**
	 * Puts an object in a cache
	 * 
	 * @param key The unique key for this cached object
	 * @param content The object to store
	 * @param groups The groups that this object belongs to
	 * @param policy The refresh policy to use
	 */
	public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy) {
		// 直接调用的Cache类的#putInCache
		getCache().putInCache(key, content, groups, policy, null);
	}

	// Cache的#putInCache()方法
	public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy,
			String origin) {
		// 首先查找这个key在缓存中是否已经存在,没有就创建一个
		CacheEntry cacheEntry = this.getCacheEntry(key, policy, origin);

		// 判断是否是新创建的缓存
		boolean isNewEntry = cacheEntry.isNew();

		// [CACHE-118] If we have an existing entry, create a new CacheEntry so
		// we can still access the old one later
		// 这里如果不是新的缓存也会新建一个CacheEntry,因为老的缓存值在后边也能访问到
		if (!isNewEntry) {
			cacheEntry = new CacheEntry(key, policy);
		}

		cacheEntry.setContent(content);
		cacheEntry.setGroups(groups);

		// 放入缓存
		cacheMap.put(key, cacheEntry);

		// Signal to any threads waiting on this update that it's now ready for them in the cache!
		// 这里会通知其他在等待值的线程结束wait
		completeUpdate(key);

		// 针对缓存事件的listener发送事件
		if (listenerList.getListenerCount() > 0) {
			CacheEntryEvent event = new CacheEntryEvent(this, cacheEntry, origin);

			if (isNewEntry) {
				dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_ADDED, event);
			} else {
				dispatchCacheEntryEvent(CacheEntryEventType.ENTRY_UPDATED, event);
			}
		}
	}



先简单的看看CacheEntry的构造函数吧,这个最基本:

	public CacheEntry(String key, EntryRefreshPolicy policy, String[] groups) {
		// CacheEntry中保存了key,所属的分组(用一个HashSet保存分组的名字,有点像Tag)
		// 还有刷新策略和创建的时间
		this.key = key;

		if (groups != null) {
			this.groups = new HashSet(groups.length);

			for (int i = 0; i < groups.length; i++) {
				this.groups.add(groups[i]);
			}
		}

		this.policy = policy;
		this.created = System.currentTimeMillis();
	}


另外还有#completeUpdate()方法:

	protected void completeUpdate(String key) {
		// Entry的更新状态,有NOT_YET_UPDATING, UPDATE_IN_PROGRESS, UPDATE_COMPLETE,
		// UPDATE_CANCELLED四种
		EntryUpdateState state;

		// Cache用一个updateStates(HashMap)来保存各个CacheEntry的更新状态
		synchronized (updateStates) {
			state = (EntryUpdateState) updateStates.get(key);

			if (state != null) {
				synchronized (state) {
					int usageCounter = state.completeUpdate();

					// 通知其他线程结束等待
					state.notifyAll();

					// 从updateStates移除key
					checkEntryStateUpdateUsage(key, state, usageCounter);
				}
			} else {
				// If putInCache() was called directly (i.e. not as a result of
				// a NeedRefreshException) then no EntryUpdateState would be
				// found.
			}
		}
	}

	// EntryUpdateState的#completeUpdate()方法
	public int completeUpdate() {
		// 状态不正确,在实际的测试中这个异常是可以报出来的
		if (state != UPDATE_IN_PROGRESS) {
			throw new IllegalStateException("Cannot complete cache update - current state (" + state
					+ ") is not UPDATE_IN_PROGRESS");
		}

		state = UPDATE_COMPLETE;
		return decrementUsageCounter();
	}

	private void checkEntryStateUpdateUsage(String key, EntryUpdateState state, int usageCounter) {
		// Clean up the updateStates map to avoid a memory leak once no thread
		// is using this EntryUpdateState instance anymore.
		// 在这里,如果没有了对这个key的"引用",那么就是updateStates中去掉它
		if (usageCounter == 0) {
			// 这里的remove操作感觉不光能像原来的注释所说的避免内存泄漏,另外还能对多线程同时put,get有用处
			EntryUpdateState removedState = (EntryUpdateState) updateStates.remove(key);
			if (state != removedState) {
				if (log.isErrorEnabled()) {
					try {
						throw new Exception("OSCache: internal error: removed state [" + removedState
								+ "] from key [" + key + "] whereas we expected [" + state + "]");
					} catch (Exception e) {
						log.error(e);
					}
				}
			}
		}
	}


这里要注意一个事情,其实在#getFromCache()是可以并发读取缓存数据的,而写入的时候只能一个线程写入,另外在写入的时候,更新实际的缓存和修改更新状态是独立开的,我在实际的测试中也发现了并发量很高的连续读写操作其实在#completeUpdate()方法中是会抛出异常的,不过在实际使用中这个情况应该发生的较少。

这里,整个存入缓存的大致流程就介绍完了,当处理多线程并发写入读取时,很多情况是要和#getFromCache()一起结合看的。

你可能感兴趣的:(多线程,jsp,cache,Google,Access)