读写锁是数据库中很常见的锁,又叫共享-排他锁,S锁和X锁。读写锁在大量读少量写的情况下有很高的效率优势。
读写锁是基于普通的互斥锁构建出来的更复杂的锁,它有两个基本特点:
1. 当任一线程持有读锁或写锁时,不允许其他线程再持有写锁
2. 当任一线程持有写锁时,不允许其他线程再持有读锁
也就是说,写锁是排他的,只要有一个线程持有写锁,就不允许其他线程再上锁。读锁是共享的,可以有多个线程持有读锁,但不允许同时持有写锁。
读锁和写锁还存在一个锁升级的问题,比如一个线程先持有了读锁,想升级成写锁,这时候基本的读写锁不能支持锁升级,有可能造成死锁,需要更新锁的操作,后面会细说更新锁。这篇只涉及到如何实现一个基本的读-写锁。
首先是一个非公平的读-写锁实现。
1. 使用一个volatile boolean write标志位来表示是否有线程持有写锁
2. 使用一个volatile 计数器来记录持有读锁的个数
3. 使用两个条件来表示条件谓词: existReadCondition, existWriteCondition
4. 需要一个内部锁来进行线程的同步,这里使用非公平的显式锁ReentrantLock
5. 当有线程持有写锁时,设置write = true,让其他线程在existWriteCondition上等待
6. 当有线程持有读锁时,让需要获得写锁的线程在existReadCondition等待,需要读锁的线程可以获得读锁,并设置读锁计数器加1
7. 释放写锁时,需要唤醒在existWriteCondition上等待的线程
8. 释放读锁时,设置读锁计数器减1,当读锁计数器等于0时表示当前没有读锁存在,唤醒在existReadCondition条件等待的线程
9. 值得注意的是这个简单的读-写锁实现,在同一时刻,不管是读锁还是写锁都需要竞争内部锁,也就是说即使是多个读锁并发,同一时刻也只有一个线程能拿到内部锁,只是这个锁的持有时间很短,只设置一个计数器就释放了。
读写锁的接口如下:
package com.zc.lock;
public interface ReadWriteLock {
public Lock readLock();
public Lock writeLock();
}
package com.zc.lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 读写锁的特点有两个:
* 1. 当读锁和写锁上锁时,不允许有写者锁上锁
* 2. 当写锁上锁时,不允许有读者锁和写者锁上锁
* 也就是说写锁是排他锁,同时只能有一个写锁
* 读锁是共享锁,可以有多个读锁存在
* **/
public class SimpleReadWriteLock implements ReadWriteLock{
private final java.util.concurrent.locks.Lock lock;
private final java.util.concurrent.locks.Condition existReadCondition;
private final java.util.concurrent.locks.Condition existWriteCondition;
private final Lock readLock;
private final Lock writeLock;
private volatile boolean write;
private volatile int readCount;
public SimpleReadWriteLock(){
lock = new ReentrantLock();
readLock = new ReadLock();
writeLock = new WriteLock();
existReadCondition = lock.newCondition();
existWriteCondition = lock.newCondition();
write = false;
readCount = 0;
}
private class ReadLock implements Lock{
@Override
public void lock() {
lock.lock();
try{
while(write){
try {
existWriteCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
readCount ++;
}finally{
lock.unlock();
}
}
@Override
public void unlock() {
lock.lock();
try{
readCount --;
if(readCount == 0){
existReadCondition.signalAll();
}
}finally{
lock.unlock();
}
}
}
private class WriteLock implements Lock{
@Override
public void lock() {
lock.lock();
try{
while(readCount > 0){
try {
existReadCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
while(write){
try {
existWriteCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
write = true;
}finally{
lock.unlock();
}
}
@Override
public void unlock() {
lock.lock();
try{
write = false;
existWriteCondition.signalAll();
}finally{
lock.unlock();
}
}
}
@Override
public Lock readLock() {
return readLock;
}
@Override
public Lock writeLock() {
return writeLock;
}
}
下面实现一个公平的读-写锁:
1. 使用公平的显式锁作为内部锁,保证先来先服务的公平性
2. 使用一个readAccquired和readReleased计数器来记录获取读锁和释放读锁的线程个数
3. 当readAccquired == readReleased时表示读锁已经全部释放,可以获得写锁
4. 最大的改进在这里,获取写锁时分为两步,第一步当没有写锁存在时,就设置write标志为true表示要获取写锁。当write为true时,后来的读锁就一直等待existWriteConditiont条件释放,而不能增加readAccquire计数器。当之前已经获得的读锁都释放后,写锁获得锁。只有等待写锁释放后,后续的读锁才能继续操作。
它的改进主要是在readAccquired == readRelease 条件谓词等待,而不是readCount > 0条件谓词等待。readAccquired的数量即肯定小于readCount,而且只要之前没有写锁存在,就优先让写锁获取。能够保证写锁快速获得锁
package com.zc.lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平的读写锁,优先让写锁能更快地获得锁,写锁尝试获得锁时,读锁会进入等待,让写锁先获得锁
*
* **/
public class FairReadWriteLock implements ReadWriteLock{
private final java.util.concurrent.locks.Lock lock;
private final java.util.concurrent.locks.Condition existReadCondition;
private final java.util.concurrent.locks.Condition existWriteCondition;
private final Lock readLock;
private final Lock writeLock;
private volatile boolean write;
private volatile int readAccquired;
private volatile int readReleased;
public FairReadWriteLock(){
lock = new ReentrantLock(true);
existReadCondition = lock.newCondition();
existWriteCondition = lock.newCondition();
readLock = new ReadLock();
writeLock = new WriteLock();
write = false;
readAccquired = 0;
readReleased = 0;
}
private class ReadLock implements Lock{
@Override
public void lock() {
lock.lock();
try{
while(write){
try {
existWriteCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
readAccquired ++;
}finally{
lock.unlock();
}
}
@Override
public void unlock() {
lock.lock();
try{
readReleased ++;
if(readReleased == readAccquired){
existReadCondition.signalAll();
}
}finally{
lock.unlock();
}
}
}
private class WriteLock implements Lock{
@Override
public void lock() {
lock.lock();
try{
while(write){
try {
existWriteCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
// 让新加入的读锁不能增加readAccquired
write = true;
while(readAccquired != readAccquired){
try {
existReadCondition.await();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted");
}
}
}finally{
lock.unlock();
}
}
@Override
public void unlock() {
lock.lock();
try{
write = false;
existWriteCondition.signalAll();
}finally{
lock.unlock();
}
}
}
@Override
public Lock readLock() {
return readLock;
}
@Override
public Lock writeLock() {
return writeLock;
}
}