并发编程:ReentrantLock读写锁

1. 写锁加锁流程

  • 代码示例

在这里插入图片描述

  • 写锁源码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结论:

  • 加锁成功
    1、锁没有被人持有
    2、锁是重入
  • 其他情况都会加锁失败

代码分析

protected final boolean tryAcquire(int acquires) {
	//1. 获取当前线程
  	Thread current = Thread.currentThread();
  	//2. 获取当前锁的状态(默认是0,表示锁没有被人持有)
    int c = getState();
    //3. 获取写锁的状态(因为读写锁是锁的同一个对象,所以为了标识读写锁,把锁的前16位标识读锁的状态,后16位标识写锁的状态)
    int w = exclusiveCount(c);
    //4. 如果c!=0,表示有人上了锁(写锁或者读锁)
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        //5. 如果w==0表示从来没上过写锁,此时的锁只能是读锁,而当前自己是要上写锁,属于锁升级,进入if加锁失败,这就是为什么不允许锁升级的原因;
        //如果进到第二个或判断 || ,表示w!=0,说明之前上过写锁,现在可能是写锁,也可能是读锁;此时判断是不是重入,如果不是重入,表示当前线程不是加了锁的线程,则current != getExclusiveOwnerThread(),也会加锁失败
        //以上两种情况都会进入if,返回false,加锁失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        //6. 如果w!=0,而且是重入,走到此行代码,判断w+1是不是大于最大重入次数65535(2的16次方减1)
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        //7. 没用超出重入的最大限制,则把w+1
        setState(c + acquires);
        return true;
    }
    //8. writerShouldBlock判断要不要排队,如果是非公平锁,writerShouldBlock()=false,compareAndSetState(c, c + acquires)进行抢锁,如果抢锁成功,因为前面有感叹号,则不会进入if,如果抢锁失败,则进入if
    //如果是公平锁,则判断队列当中有没有人排队,如果有人排队,则writerShouldBlock()=true,进入if;如果没人排队,则抢锁compareAndSetState(c, c + acquires)
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    //9. 加锁成功则把当前持有锁的线程设置为自己
    setExclusiveOwnerThread(current);
    return true;
}

2. 读锁加锁流程

  • 读锁源码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结论:

  • 加锁成功
    1、没人上写锁
    2、重入锁降级
  • 其他情况都会加锁失败

代码分析

protected final int tryAcquireShared(int unused) {
	//1. 获取当前线程
    Thread current = Thread.currentThread();
    //2. 获取当前锁的状态(默认是0,表示锁没有被人持有)
    int c = getState();
    //3. 如果exclusiveCount(c) != 0表示上了写锁
    //4. 如果exclusiveCount(c) != 0,进入第二个判断,getExclusiveOwnerThread() != current表示不是重入
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //5. 如果上面的if没有进,可能有2种情况,1、没人上写锁,2、有人上写锁,而且是重入,有加了读锁,属于锁降级(先写锁后读锁)
    int r = sharedCount(c);
    //6. 判断需不需要排队,是不是小于最大的加锁次数,加锁有没有成功
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        //7. r==0表示这是第一次加读锁
        if (r == 0) {
        	//8. 把当前线程赋给局部变量firstReader
            firstReader = current;
            //9. 记录当前线程的加锁次数
            firstReaderHoldCount = 1;
        //10. 如果是重入,则r==1,当前线程和上一个线程是同一个,会进入else if,firstReaderHoldCount变为2
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
        //11. 如果第一个线程已经释放了锁,这时第二次线程来加读锁,则r=1,而且不是重入,就会进入else内
        //先把一个为null的局部变量cachedHoldCounter赋给rh
            HoldCounter rh = cachedHoldCounter;
            //12. 第二个线程来加锁,则rh==null,进入if
            //12.1 如果是第三个线程来加锁,rh!=null,因为第二个线程加锁的时候,给rh赋过值了,进入第二个判断,第二个线程的id肯定不等于当前线程的id
            if (rh == null || rh.tid != getThreadId(current))
            //13. get()方法看下面的代码分析,当第二个线程执行到这时,get方法返回的是一个new出来的HoldCounter对象,赋给rh和cachedHoldCounter,而且在ThreadLocalMap内把后面的线程都保存了
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            //14. HoldCounter对象内的count+1
            rh.count++;
        }
        //15. 加锁成功,返回1
        return 1;
    }
    return fullTryAcquireShared(current);
}


public T get() {
    Thread t = Thread.currentThread();
    //1. 从当前线程的ThreadLocal中得到map
    ThreadLocalMap map = getMap(t);
    //2. 第二线程进来,map内肯定是什么都没有存的,==nul,不会进if
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //3. 
    return setInitialValue();
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    //2. 第三个线程过来,map!=null(因为被第二个线程创建了),进入if,把线程信息保存到ThreadLocalMap中
    if (map != null)
        map.set(this, value);
    //1. 第二个线程过来,map==null,执行到这里,创建ThreadLocalMap,将value存入
    else
        createMap(t, value);
    return value;
}

public HoldCounter initialValue() {
    return new HoldCounter();
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

//HoldCounter 的数据结构,count计数,tid存当前线程id
static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = getThreadId(Thread.currentThread());
}
  • t1加读锁

在这里插入图片描述

  • t2加读锁

在这里插入图片描述

  • t3加读锁

在这里插入图片描述

  • 上图可以看出,读锁加锁后,第一个线程id存在firstReader,加锁次数存在firstReaderHoldCount,都是ReentrantLock内的全局变量;
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
  • 最后一个锁存在cachedHoldCounter,有加锁次数count和线程id,也是ReentrantLock内的全局变量;
private transient HoldCounter cachedHoldCounter;
static final class HoldCounter {
     int count = 0;
     // Use id, not reference, to avoid garbage retention
     final long tid = getThreadId(Thread.currentThread());
}
  • 使用全局变量来提高获取线程信息的效率
  • 其余线程信息都放在ThreadLocalMap中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值