独占锁:指该锁一次只能被一个线程所持有。对 ReentrantLock 和 Synchronized 而言都是独占锁
共享锁:指该锁可被多个线程所持有。
ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥。
通俗理解:机场大屏幕,每个人都能看到,这就是读共享;但是在在机场通知旅客飞机延误的只能是一个人,如果一群广播员播放,将会乱套,这就是独占写锁。同时在播报的时候,屏幕信息就不能更改,这就是互斥。 即 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是如果有一个线程想去写共享资源,就不应该再有其他线程可以对该资源进行读或写。
一、代码展示
正面例子
共享缓存
public class MyCache {/*** volatile关键字可以查看前面的包* 管缓存的,必须用volatile,保证可见性和禁止指令重排,一个线程对其进行了修改,必须让其它线程知道*/private volatile Map<String, Object> map = new HashMap<>();/*** 可以对比加了读写锁和没加读写锁的区别*/private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();/*** 写操作:原子+独占,整个写过程必须完整的统一体,不能被分割和打断** @param key* @param value*/public void put(String key, Object value) {readWriteLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + " 正在写入:" + "key:" + key + ",value:" + value);// 暂定一会模拟网络延迟try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}map.put(key, value);System.out.println(Thread.currentThread().getName() + " 写入完成:" + "key:" + key + ",value:" + value);} catch (Exception e) {e.printStackTrace();} finally {readWriteLock.writeLock().unlock();}}public void get(String key) {readWriteLock.readLock().lock();try {System.out.println(Thread.currentThread().getName() + " 正在读取:" + "key:" + key);// 暂定一会模拟网络延迟try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}Object result = map.get(key);System.out.println(Thread.currentThread().getName() + " 读取完成:" + "key:" + key + ",result:" + result);} catch (Exception e) {e.printStackTrace();} finally {readWriteLock.readLock().unlock();}}public void clearMap() {map.clear();}}
测试
public class ReadWriteLockDemo {public static void main(String[] args) {MyCache myCache = new MyCache();// MyCacheNoLock myCache = new MyCacheNoLock();for (int i = 0; i < 5; i++) {final int i1 = i;new Thread(() -> {myCache.put(i1 + "", i1 + "");}, String.valueOf(i)).start();new Thread(() -> {myCache.get(i1 + "");}, String.valueOf(i)).start();}}}
结果
0 正在写入:key:0,value:00 写入完成:key:0,value:00 正在读取:key:00 读取完成:key:0,result:01 正在写入:key:1,value:11 写入完成:key:1,value:11 正在读取:key:11 读取完成:key:1,result:12 正在写入:key:2,value:22 写入完成:key:2,value:22 正在读取:key:22 读取完成:key:2,result:23 正在写入:key:3,value:33 写入完成:key:3,value:33 正在读取:key:33 读取完成:key:3,result:34 正在写入:key:4,value:44 写入完成:key:4,value:44 正在读取:key:44 读取完成:key:4,result:4
可以很明显的看到,先写后读的,这样可以保证每次读到的数据都是最新的。展现了读写锁的,读(共享),写(独占),读写(互斥)
反面例子
以下代码没有加读写锁
public class MyCacheNoLock {/*** volatile关键字可以查看前面的包* 管缓存的,必须用volatile,保证可见性和禁止指令重排,一个线程对其进行了修改,必须让其它线程知道*/private volatile Map<String, Object> map = new HashMap<>();/*** 写操作:原子+独占,整个写过程必须完整的统一体,不能被分割和打断** @param key* @param value*/public void put(String key, Object value) {System.out.println(Thread.currentThread().getName() + " 正在写入:" + "key:" + key + ",value:" + value);// 暂定一会模拟网络延迟try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}map.put(key, value);System.out.println(Thread.currentThread().getName() + " 写入完成:" + "key:" + key + ",value:" + value);}public void get(String key) {System.out.println(Thread.currentThread().getName() + " 正在读取:" + "key:" + key);// 暂定一会模拟网络延迟try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}Object result = map.get(key);System.out.println(Thread.currentThread().getName() + " 读取完成:" + "key:" + key + ",result:" + result);}public void clearMap() {map.clear();}}
结果
0 正在写入:key:0,value:02 正在写入:key:2,value:21 正在写入:key:1,value:14 正在写入:key:4,value:43 正在写入:key:3,value:30 正在读取:key:01 正在读取:key:12 正在读取:key:23 正在读取:key:34 正在读取:key:43 写入完成:key:3,value:32 写入完成:key:2,value:24 写入完成:key:4,value:41 写入完成:key:1,value:10 写入完成:key:0,value:00 读取完成:key:0,result:null // 没有读取到1 读取完成:key:1,result:13 读取完成:key:3,result:null2 读取完成:key:2,result:null4 读取完成:key:4,result:4
可以看到,还没有写入完成,就开始读取了,导致一些数据没有读取到。
二、总结
- 读-读共存
读-写不能共存
写-写不能共存
写操作:原子+独占,整个写过程必须完整的统一体,不能被分割和打断
�
