并发编程 -- 读写锁

一,读写锁简介

之前说的ReentrantLock是一种排他锁,和synchronized类似,排他锁保证同一时刻只有一个线程进行访问,但是读写锁是维护一对读锁和写锁,读锁允许在同一时刻可以多个线程并发访问一个方法,写锁在访问时,所有读锁和其余写锁局不能访问,并发性比排他所高

二,ReentrantReadWriteLock简单使用

1.使用ReentrantReadWriteLock维护一个线程不安全的hashmap

public class ReentrantReadWriteLockTest {

    //通过读写锁来维护一个线程不安全的hashmap
    static HashMap<String,Object> hashMap = new HashMap<>();
    //获取读写锁
    static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    //获取读锁
    static Lock readLock = reentrantReadWriteLock.readLock();
    //获取写锁
    static Lock writeLock = reentrantReadWriteLock.writeLock();

    /**
     * 获取值  ----> 使用读锁,在同一时刻多个线程可以并发访问该方法,而不会被阻塞
     * @param key
     * @return
     */
    public static final Object getValue(String key){
        readLock.lock();
        try{
            return hashMap.get(key);
        }finally {
            readLock.unlock();
        }
    }

    /**
     * 写入map ------> 使用写锁,在同一时刻只有线程访问,只有当该线程释放锁,其他线程才可以访问
     * @param key
     * @param value
     */
    public static final void putValue(String key,Object value){
        writeLock.lock();
        try{
            hashMap.put(key, value);
        }finally {
            writeLock.unlock();
        }
    }
}

2.原理解析

ReentrantReadWriteLock实现了ReadWriteLock接口,ReadWriteLock接口定义了写锁和读锁两个方法用来分别获取写锁和读锁

public interface ReadWriteLock {
    Lock readLock();

    Lock writeLock();
}

ReentrantReadWriteLock默认是非公平锁,但是也支持公平锁

    public ReentrantReadWriteLock() {
        this(false);
    }
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

获取读锁和写锁

属性中的读锁和写锁是私有属性,通过这两个方法暴露出去。

public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

读写状态的设计

读写锁也是基于同步器实现的,所以其同步状态就是同步器的同步状态,但是读写锁是一对锁,不同于之前的排它锁,读写锁是通过位运算来维护读状态和写状态的。

读写锁需要在其自定义同步器上维护多个读线程和一个写线程

读状态

读状态由高16位维护,当前状态表示获取了一次读状态

假设当前同步器状态为S,则读状态为 S>>>16

获取一次读状态,则+1 —> S+(1<<16)

写状态

写状态由低16位维护,当前状态表示获取了一次写状态,且重入了2次

写状态为:S&0x0000FFFF

每重入一次写状态,则+1

        // 中间值
        static final int SHARED_SHIFT   = 16;
        // 由于读锁用高位部分,所以读锁个数加1,其实是状态值加 2^16
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        // 写锁的可重入的最大次数、读锁允许的最大数量
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        // 写锁的掩码,用于状态的低16位有效值
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        //当前持有读锁的线程数 ---> c:一般传入getStatus():同步状态值
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

        //写锁重入次数 ---> c:一般传入getStatus():同步状态值
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

读锁的获取和释放

读锁是一个支持重进入的共享锁,他能够被多个线程同时获取,在没有其他线程访问或者写状态为0时,读锁总能被成功的获取,且线程安全的增加读状态,如果当前线程已经获得了读锁,就增加读状态,如果当前线程在获取读锁时,写锁状态不为0,则进入等待状态

读状态的获取

        protected final int tryAcquireShared(int unused) {
            
            Thread current = Thread.currentThread();
            int c = getState();
            //写状态!=0或者写锁被另一个线程持有,则失败
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //获取持有读锁的线程数量    
            int r = sharedCount(c);
            //是否需要排队(是否是公平模式)&&是否小于最大线程数&&CAS增加读状态
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    //如果之前没有线程获取读锁,则记录第一个读者是当前线程
                    firstReader = current;
                    //第一个读者重入次数为1
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    //如果获取读锁的线程是第一个读者,则其重入状态+1
                    firstReaderHoldCount++;
                } else {
                    // 如果有线程获取了读锁且当前线程不是第一个读者
                    // 则从缓存中获取重入次数保存器
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    //重入+1    
                    rh.count++;
                }
                return 1;
            }
            // 通过这个方法再去尝试获取读锁(如果之前其它线程获取了写锁,一样返回-1表示失败)
            return fullTryAcquireShared(current);
        }

读状态的释放

读锁的每次释放都会减少读状态,减少的值是1<<16,释放读状态过程线程安全

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 清理firstReader缓存 或 readHolds里的重入计数
    //第一个读者是当前线程,且重入次数>0
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        //清空重入次数和移除读者
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        //第一个读者不是当前线程
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            // 完全释放读锁
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count; // 主要用于重入退出
    }
    // 循环在CAS更新状态值,主要是把读锁数量减 1,所以是线程安全的
    for (;;) {
        int c = getState();
        //每次减少值为:1<<16
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // 释放读锁对其他读线程没有任何影响,
            // 但可以允许等待的写线程继续,如果读锁、写锁都空闲。
            return nextc == 0;
    }
}

写锁的获取和释放

写锁是一个支持重入的排它锁,如果当前线程已经获取了写状态,则重入增加写状态,如果当前线程在获取写锁时,读锁已经被获取,此时读状态不为0,同步器状态不为0,或者该线程不是已经获取写锁的线程,则线程进入等待状态

写锁的获取

        final boolean tryWriteLock() {
            //获取当前线程
            Thread current = Thread.currentThread();
            //获取同步器状态
            int c = getState();
            //同步器状态不为0
            if (c != 0) {
                //获取写锁重入次数
                int w = exclusiveCount(c);
                //存在读锁或者当前获得写锁的线程不是当前线程
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //超过最大线程数量
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            //CAS设置同步器状态 : 写状态+1
            if (!compareAndSetState(c, c + 1))
                return false;
            //设置当前获取写锁的线程是当前线程    
            setExclusiveOwnerThread(current);
            return true;
        }

写锁的释放

写锁的释放和排他锁的释放过程相似,每次释放都会减少写状态,当写状态为0时,表示写锁已经被成功释放

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            //同步队列
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
 
protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //写状态-1    
            int nextc = getState() - releases;
            //判断写状态是否为0
            boolean free = exclusiveCount(nextc) == 0;
            //如果为0,则写状态成功释放,释放线程
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值