您的位置:首页 > 其它

聊聊高并发(十五)实现一个简单的读-写锁(共享-排他锁)

2017-02-09 15:29 531 查看
读写锁是数据库中很常见的锁,又叫共享-排他锁,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. 值得注意的是这个简单的读-写锁实现,在同一时刻,不管是读锁还是写锁都需要竞争内部锁,也就是说即使是多个读锁并发,同一时刻也只有一个线程能拿到内部锁,只是这个锁的持有时间很短,只设置一个计数器就释放了。

读写锁的接口如下:

[java] view
plain copy

package com.zc.lock;

public interface ReadWriteLock {

public Lock readLock();

public Lock writeLock();

}

非公平的读-写锁实现

[java] view
plain copy

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;

}

}

非公平的读-写锁在大量读存在的情况下有饥饿的问题,写锁在readCount > 0这个条件谓词下等待,也就是说一旦有读锁存在,写操作就要等待,而读操作一般都是远高于写操作的,并且没有先来先服务的公平性,后来的读操作很可能快于写操作获得锁。写锁有可能长时间的等待锁而不能获取锁。

下面实现一个公平的读-写锁:
1. 使用公平的显式锁作为内部锁,保证先来先服务的公平性
2. 使用一个readAccquired和readReleased计数器来记录获取读锁和释放读锁的线程个数
3. 当readAccquired == readReleased时表示读锁已经全部释放,可以获得写锁
4. 最大的改进在这里,获取写锁时分为两步,第一步当没有写锁存在时,就设置write标志为true表示要获取写锁。当write为true时,后来的读锁就一直等待existWriteConditiont条件释放,而不能增加readAccquire计数器。当之前已经获得的读锁都释放后,写锁获得锁。只有等待写锁释放后,后续的读锁才能继续操作。
它的改进主要是在readAccquired == readRelease 条件谓词等待,而不是readCount > 0条件谓词等待。readAccquired的数量即肯定小于readCount,而且只要之前没有写锁存在,就优先让写锁获取。能够保证写锁快速获得锁

[java] view
plain copy

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;

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐