学习自尚硅谷
独占锁(写锁) / 共享锁(读锁) / 互斥锁
概念
独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁
共享锁:指该锁可以被多个线程锁持有
对ReentrantReadWriteLock其读锁是共享,其写锁是独占
写的时候只能一个人写,但是读的时候,可以多个人同时读
为什么会有写锁和读锁
原来我们使用ReentrantLock创建锁的时候,是独占锁,也就是说一次只能一个线程访问,但是有一个读写分离场景,读的时候想同时进行,因此原来独占锁的并发性就没这么好了,因为读锁并不会造成数据不一致的问题,因此可以多个人共享读
1
| 多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行,但是如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写
|
读-读:能共存
读-写:不能共存
写-写:不能共存
代码实现
实现一个读写缓存的操作,假设开始没有加锁的时候,会出现什么情况
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock;
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) { System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } map.put(key, value); System.out.println(Thread.currentThread().getName() + "\t 写入完成"); }
public void get(String key) { System.out.println(Thread.currentThread().getName() + "\t 正在读取:"); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } Object value = map.get(key); System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value); }
} public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache(); for (int i = 0; i < 5; i++) { final int tempInt = i; new Thread(() -> { myCache.put(tempInt + "", tempInt + ""); }, String.valueOf(i)).start(); } for (int i = 0; i < 5; i++) { final int tempInt = i; new Thread(() -> { myCache.get(tempInt + ""); }, String.valueOf(i)).start(); } } }
|
我们分别创建5个线程写入缓存
1 2 3 4 5 6 7 8
| for (int i = 0; i < 5; i++) { final int tempInt = i; new Thread(() -> { myCache.put(tempInt + "", tempInt + ""); }, String.valueOf(i)).start(); }
|
5个线程读取缓存,
1 2 3 4 5 6 7 8
| for (int i = 0; i < 5; i++) { final int tempInt = i; new Thread(() -> { myCache.get(tempInt + ""); }, String.valueOf(i)).start(); }
|
最后运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 0 正在写入:0 4 正在写入:4 3 正在写入:3 1 正在写入:1 2 正在写入:2 0 正在读取: 1 正在读取: 2 正在读取: 3 正在读取: 4 正在读取: 2 写入完成 4 写入完成 4 读取完成:null 0 写入完成 3 读取完成:null 0 读取完成:null 1 写入完成 3 写入完成 1 读取完成:null 2 读取完成:null
|
我们可以看到,在写入的时候,写操作都被其它线程打断了,这就造成了,还没写完,其它线程又开始写,这样就造成数据不一致。例如:0写入和0写入完成中间被打断好多次
解决方法
上面的代码是没有加锁的,这样就会造成线程在进行写入操作的时候,被其它线程频繁打断,从而不具备原子性,这个时候,我们就需要用到读写锁来解决了
1 2 3 4 5
|
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
|
当我们在进行写操作的时候,就需要转换成写锁
1 2 3 4 5
| rwLock.writeLock().lock();
rwLock.writeLock().unlock();
|
当们在进行读操作的时候,在转换成读锁
1 2 3 4 5
| rwLock.readLock().lock();
rwLock.readLock().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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入完成");
} catch (Exception e) { e.printStackTrace(); } finally { rwLock.writeLock().unlock(); } }
public void get(String key) {
rwLock.readLock().lock(); try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
} catch (Exception e) { e.printStackTrace(); } finally { rwLock.readLock().unlock(); } }
public void clean() { map.clear(); }
} public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <= 5; i++) { final int tempInt = i; new Thread(() -> { myCache.put(tempInt + "", tempInt + ""); }, String.valueOf(i)).start(); }
for (int i = 1; i <= 5; i++) { final int tempInt = i; new Thread(() -> { myCache.get(tempInt + ""); }, String.valueOf(i)).start(); } } }
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 1 正在写入:1 1 写入完成 2 正在写入:2 2 写入完成 3 正在写入:3 3 写入完成 4 正在写入:4 4 写入完成 5 正在写入:5 5 写入完成 2 正在读取: 3 正在读取: 1 正在读取: 4 正在读取: 5 正在读取: 2 读取完成:2 1 读取完成:1 4 读取完成:4 3 读取完成:3 5 读取完成:5
|
从运行结果我们可以看出,写入操作是一个一个线程进行执行的,并且中间不会被打断,而读操作的时候,是同时5个线程进入,然后并发读取操作