读写锁
1、允许多个线程同时读共享变量;
2、只允许一个线程写共享变量;
3、如果一个写线程正在执行写操作,此时禁止读线程读共享变量。
实现一个缓存
我们声明一个Cache类,然后里面用HashMap来实现存储,但是它并不是线程安全的,我们采用ReadWriteLock来保证线程安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Cache<K,V> { final Map<K, V> m = new HashMap<>(); final ReadWriteLock rwl = new ReentrantReadWriteLock(); final Lock r = rwl.readLock(); final Lock w = rwl.writeLock(); V get(K key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } V put(K key, V value) { w.lock(); try { return m.put(key, v); } finally { w.unlock(); } } }
|
注意,在并发的场景下,写缓存时,可能会存在缓存的覆盖
实现缓存的按需加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class Cache<K,V> { final Map<K, V> m = new HashMap<>(); final ReadWriteLock rwl = new ReentrantReadWriteLock(); final Lock r = rwl.readLock(); final Lock w = rwl.writeLock();
V get(K key) { V v = null; r.lock(); ① try { v = m.get(key); ② } finally{ r.unlock(); ③ } if (v != null) { ④ return v; } w.lock(); ⑤ try { v = m.get(key); ⑥ if (v == null) { ⑦ v = 省略代码无数 m.put(key, v); } } finally{ w.unlock(); } return v; } }
|
读写锁的升级与降级
上面按需加载的示例代码中,在①处获取读锁,在③处释放读锁,那是否可以在②处的下面增加验证缓存并更新缓存的逻辑呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
r.lock(); ① try { v = m.get(key); ② if (v == null) { w.lock(); try { } finally { w.unlock(); } } } finally { r.unlock(); ③ }
|
这样看上去好像是没有问题的,先是获取读锁,然后再升级为写锁,对此还有个专业的名字,叫锁的升级。可惜 ReadWriteLock 并不支持这种升级。在上面的代码示例中,读锁还没有释放,此时获取写锁,会导致写锁永久等待,最终导致相关线程都被阻塞,永远也没有机会被唤醒。
锁的降级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class CachedData { Object data; volatile boolean cacheValid; final ReadWriteLock rwl = new ReentrantReadWriteLock(); final Lock r = rwl.readLock(); final Lock w = rwl.writeLock();
void processCachedData() { r.lock(); if (!cacheValid) { r.unlock(); w.lock(); try { if (!cacheValid) { data = ... cacheValid = true; } r.lock(); ① } finally { w.unlock(); } } try { use(data); } finally { r.unlock(); } } }
|