Ehcache的并发支持

在高并发的情况下, 使用Ehcache缓存时, 由于并发的读与写, 我们读的数据有可能是错误的, 我们写的数据也有可能意外的被覆盖. 所幸的是Ehcache为我们提供了针对于缓存元素KeyRead(读)/Write(写)锁. Key的read锁可以同时被多个线程持有, 但要等到这些线程都释放掉read锁后, 其他线程才能获得write锁, 而且同一时间这个key的write锁只能被一个线程持有.


当一个线程取得某一缓存key的write锁之后:

1) 此线程可以取得该key的read锁(即此线程可以读取该key的值);
2) 其他线程不可以取得该key的read锁和write锁(即其他线程不能读取或写入该key的值). 要等到该线程释放该key的write锁后才能获得.

例子:

private static void testWriteLock() {
	Cache cache = cacheManager.getCache("cache2");
	String key = "key1";
	
	Thread t1 = new Thread(new Runnable() {
		
		@Override
		public void run() {
			try {
				cache.acquireWriteLockOnKey(key);
				for (int i = 0; i < 5; i++) {
					cache.put(new Element(key, i));
					System.out.println("thread1 put " + i + " to " + key);
					Thread.sleep(500);
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				cache.releaseWriteLockOnKey(key);
				System.out.println("thread1 end.");
			}
		}
	});
	
	Thread t2 = new Thread(new Runnable() {
		
		boolean canRead = false;
		boolean canWrite = false;
		
		@Override
		public void run() {
			try {
				// 测试一
				while(true) {
					Thread.sleep(300);
					if (cache.tryWriteLockOnKey(key, 1)) {
						System.out.println("thread2 get write lock success.");
						canWrite = true;
					} else {
						System.out.println("thread2 cannot get write lock.");
					}
					
					if (cache.tryReadLockOnKey(key, 1)) {
						System.out.println("thread2 get read lock success.");
						canRead = true;
					} else {
						System.out.println("thread2 cannot get read lock.");
					}
					
					if (canRead && canWrite) {
						break;
					}
				}
				// 测试一
				
				// 测试二
//					Thread.sleep(1000);
//					for (int i = 10; i < 15; i++) {
//						cache.put(new Element(key, i));
//						System.out.println("thread2 put " + i + " to " + key);
//					}
				// 测试二
				
				// 测试三
//					Thread.sleep(1000);
//					System.out.println("thread2 get value of " + key + ": " + cache.get(key).getObjectValue());
				// 测试三
				
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				System.out.println("thread2 end.");
			}
		}
	});
	
	t1.start();
	t2.start();
}

线程1首先运行, 它先获取元素"key1"的write锁, 然后每0.5秒更新key1的值. 线程2后运行.

在测试例子一里, 线程2每隔0.3秒判断一次能否获取"key1"的read锁和write锁, 均失败. 等到线程1结束并释放"key1"的write锁后, 线程2才能取得"key1"的read锁和write锁.

运行结果如下:

thread1 put 0 to key1
thread2 cannot get write lock.
thread2 cannot get read lock.
thread1 put 1 to key1
thread2 cannot get write lock.
thread2 cannot get read lock.
thread2 cannot get write lock.
thread2 cannot get read lock.
thread1 put 2 to key1
thread2 cannot get write lock.
thread2 cannot get read lock.
thread1 put 3 to key1
thread2 cannot get write lock.
thread2 cannot get read lock.
thread2 cannot get write lock.
thread2 cannot get read lock.
thread1 put 4 to key1
thread2 cannot get write lock.
thread2 cannot get read lock.
thread2 cannot get write lock.
thread2 cannot get read lock.
thread1 end.
thread2 get write lock success.
thread2 get read lock success.
thread2 end.

在测试例子二里, 线程2尝试直接更新key1的值. 但 因为此时key1的write锁在线程1手上, 所以线程2运行到这里会暂时阻塞(不会报错), 直到线程1结束并释放"key1"的write锁后, 线程2才会继续运行.

运行结果如下:

thread1 put 0 to key1
thread1 put 1 to key1
thread1 put 2 to key1
thread1 put 3 to key1
thread1 put 4 to key1
thread1 end.
thread2 put 10 to key1
thread2 put 11 to key1
thread2 put 12 to key1
thread2 put 13 to key1
thread2 put 14 to key1
thread2 end.


在测试例子三里, 线程2尝试读取key1的值.  因为此时 key1的write锁在线程1手上, 所以线程2运行到这里会暂时阻塞(不会报错), 直到线程1结束并释放"key1"的write锁后,  线程2 才会继续运行.

运行结果如下:

thread1 put 0 to key1
thread1 put 1 to key1
thread1 put 2 to key1
thread1 put 3 to key1
thread1 put 4 to key1
thread1 end.
thread2 get value of key1: 4
thread2 end.


当一个线程取得某一缓存key的read锁之后:
1) 此线程不可以取得该key的write锁(即此线程不能写入该key的值);
2) 其他线程可以取得该key的read锁, 但不能取得write锁(即其他线程可以读取该key的值, 但不能写入). 要等到该线程(或所有线程)释放该key的read锁后, 才能获得write锁.

例子:

private static void testReadLock() {
	Cache cache = cacheManager.getCache("cache2");
	String key = "key1";
	cache.put(new Element(key, "value"));
	
	Thread t1 = new Thread(new Runnable() {
		
		int i = 0;
		@Override
		public void run() {
			try {
				cache.acquireReadLockOnKey(key);
				System.out.println("thread1 get read lock success.");
				
				// 测试一
				while(true) {
					Thread.sleep(500);
					if (cache.tryWriteLockOnKey(key, 1)) {
						System.out.println("thread1 get write lock success.");
						break;
					} else {
						System.out.println("thread1 cannot get write lock.");
						i++;
						
						if (i == 5) {
							cache.releaseReadLockOnKey(key);
						}
					}
				}
				// 测试一
				
				// 测试二
//					cache.put(new Element(key, "value1"));
//					System.out.println("thread1 put value1 to " + key + " success.");
				// 测试二
				
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				cache.releaseWriteLockOnKey(key);
				System.out.println("thread1 end.");
			}
		}
	});
	
	t1.start();
}
在测试例子一里, 线程1获得"key1"的read锁后, 不能继续获取write锁, 要等到read锁释放后, 才能获得write锁.

运行结果如下:

thread1 get read lock success.
thread1 cannot get write lock.
thread1 cannot get write lock.
thread1 cannot get write lock.
thread1 cannot get write lock.
thread1 cannot get write lock.
thread1 release read lock.
thread1 get write lock success.
thread1 end.

在测试例子二里, 线程1尝试更新"key1"的值. 但因为此时线程1持有了"key1"的read锁, 所以此时方法会进入阻塞并死锁(因为线程1不会释放read锁).

运行结果如下:

thread1 get read lock success.
(没有打印"thread1 end.")

其他线程可以获取read锁, 但不能获取write锁的例子暂时省略..


最后注意, acquireReadLockOnKey()和acquireWriteLockOnKey()方法都不会报错, 最多使进入阻塞状态. 但对于releaseReadLockOnKey()和releaseWriteLockOnKey()方法, 如果持有read锁或write锁的线程不是本线程, 或者本线程之前已经释放掉read锁或write锁, 那么再次调用这两个方法会报错. 错误信息如下:

java.lang.IllegalMonitorStateException: attempt to unlock read lock, not locked by current thread
	at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.unmatchedUnlockException(ReentrantReadWriteLock.java:444)
	at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryReleaseShared(ReentrantReadWriteLock.java:428)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.releaseShared(AbstractQueuedSynchronizer.java:1341)
	at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.unlock(ReentrantReadWriteLock.java:881)
	at net.sf.ehcache.concurrent.ReadWriteLockSync.unlock(ReadWriteLockSync.java:64)
	at net.sf.ehcache.Cache.releaseLockOnKey(Cache.java:3953)
	at net.sf.ehcache.Cache.releaseReadLockOnKey(Cache.java:4008)
	at com.demo.cache.TestCacheManager$1.run(TestCacheManager.java:46)
	at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-2" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryRelease(ReentrantReadWriteLock.java:371)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock.unlock(ReentrantReadWriteLock.java:1131)
	at net.sf.ehcache.concurrent.ReadWriteLockSync.unlock(ReadWriteLockSync.java:64)
	at net.sf.ehcache.Cache.releaseLockOnKey(Cache.java:3953)
	at net.sf.ehcache.Cache.releaseWriteLockOnKey(Cache.java:4017)
	at com.demo.cache.TestCacheManager$1.run(TestCacheManager.java:48)
	at java.lang.Thread.run(Thread.java:745)



  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值