ReadWriteLock 是 JDK 中的读写锁接口,提供了 readLock 和 writeLock 两种锁的操作机制,一个是读锁,一个是写锁。
ReadWriteLock同Lock一样也是一个接口,ReentrantLock 是Lock的一种实现,ReentrantReadWriteLock 是 ReadWriteLock 的一种实现。
ReadWriteLock 中只有写锁支持Condition,读锁不支持,读锁调用 newCondition() 方法,会抛出 UnsupportedOperationException 异常
读写锁非常适合读多写少的场景。读写锁与互斥锁的一个重要区别是读写锁允许多个线程同时读共享变量,这是读写锁在读多写少的情况下性能较高的原因。
1、读写锁的原则:
多个线程可同时读共享变量
只允许一个线程写共享变量
写线程正在执行写操作,禁止其他线程读写共享变量
2、读写锁互斥原则:
读-读能共存,
读-写不能共存,
写-写不能共存。
例子:
public class ReadWriteLockDemo {
final static ReadWriteLock rwLock = new ReentrantReadWriteLock();
final static Lock readLock = rwLock.readLock();//读锁
final static Lock writeLock = rwLock.writeLock();//写锁
static int count = 0;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "==" + get());
}, "read").start();
}
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "==add");
add(5);
}, "write").start();
}
}
private static int get() {
//使用读锁
readLock.lock();
try {
return count;
} finally {
readLock.unlock();
}
}
private static void add(int num) {
//使用写锁
writeLock.lock();
try {
count = count + num;
} finally {
writeLock.unlock();
}
}
}
二、锁的一些知识点
1、公平锁和非公平锁
概念
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象
公平锁和非公平锁区别
公平锁:在并发坏境中.每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁.否则就会加入到等待队列中.以后会按照FIFO的规则从队列中取到自己。
非公平锁:非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。
ReentrantLock 和 ReadWriteLock 而言,通过构造函数指定该锁是否为公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
synchronized 而言,也是一种非公平锁。
2、可重入锁
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
可重入锁最大的作用是避免死锁
可重入特性还允许从写锁降级到读锁—通过获取写锁,然后获取读锁,然后释放写锁。但是,从读锁到写锁的升级是不可能的。
3、锁降级
锁降级指的是把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前有用的)写锁的过程。通过这种重入,可以减少一步流程(释放写锁后 再次 获取读锁)。使用了锁降级,就可以减去释放写锁的步骤。直接获取读锁。效率更高。
当前线程拥有写锁,然后将其释放,最后再获取读锁,这种并不能称之为锁降级,
实例:
public class CachedData {
//模拟共享数据
private String data = "原来的数据";
//volatile修饰,保持内存可见性,数据是不是最新的
volatile boolean isUpdate;
//可重入读写锁
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
// 模拟放置数据到缓存
public void processCachedData(String d) {
//获取写锁之前,首先获取读锁
rwl.readLock().lock();
//发现数据不是最新的则放弃读锁(读锁不能升级),获取写锁
if (!isUpdate) {
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (!isUpdate) {
data = d; //拿到写锁后,把新的数据写入
isUpdate = true;
}
rwl.readLock().lock(); //拥有写锁的请况下金额直接获取读锁
} finally {
//同时拥有读锁和写锁,在这里释放写锁,进行锁降级
rwl.writeLock().unlock();
}
}
try {
System.out.println("最新的数据打印:" + data);
} finally {
rwl.readLock().unlock();
}
}
public static void main(String[] args) {
CachedData cachedData = new CachedData();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
cachedData.processCachedData(Thread.currentThread().getName() + "=新数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
通常可以在集合使用场景中看到ReentrantReadWriteLock的使用。不过只有在集合比较大,读操作比写操作多,操作开销大于同步开销的时候才是值得的。
public class ReadWriteLockDemo2 {
private final Map m = new TreeMap();
//读写锁
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//获取读锁
private final Lock r = rwl.readLock();
//获取写锁
private final Lock w = rwl.writeLock();
public Object get(String key) {
r.lock();
try {
return m.get(key);
} finally {
r.unlock();
}
}
public String[] allKeys() {
r.lock();
try {
Set rsSet = m.keySet();
return rsSet.toArray(new String[rsSet.size()]);
} finally {
r.unlock();
}
}
public Object put(String key, Object value) {
w.lock();
try {
return m.put(key, value);
} finally {
w.unlock();
}
}
public void clear() {
w.lock();
try {
m.clear();
} finally {
w.unlock();
}
}
}
—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。