锁降级

锁降级概念

锁降级:当前线程获得写锁,没有释放写锁的情况下再去获得读锁,然后释放写锁,这个过程就是锁降级
(当前线程持有锁的状态由写锁降到读锁就是锁降级)
使用场景:当多线程情况下,更新完数据要立刻查询刚更新完的数据
(因更新完数据释放写锁后还持有读锁,所有线程要获得写锁都要等待读锁释放,这时持有读锁的线程可以查到刚更新完的数据)
弊端:适合读多写少的场景,如果锁降级的同时设置成了非公平锁可能会导致写锁很长时间获得不到

ReentrantReadWriteLock

ReentrantReadWriteLock支持锁降级,但是不支持锁升级
下面代码说明ReentrantReadWriteLock锁降级的使用

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {

    //默认是非公平锁
    //ReentrantReadWriteLock 支持锁降级 不支持锁升级
    ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock(true);
    //读锁
    ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    //写锁
    ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private  int i=0;

    public static void main(String[] args) {

        //锁升级  当前线程持有读锁,然后获得写锁,将读锁释放,这样就完成了锁升级
        //锁降级  当前线程持有写锁,然后获得读锁,将写锁释放,这样就完成了锁降级

//锁降级
//        writeLock.lock();
//        System.out.println("获得写锁");
//        readLock.lock();
//        System.out.println("获得读锁");
//        writeLock.unlock();
//        System.out.println("获得释放写锁");
//        readLock.unlock();
//        System.out.println("释放读锁");

//锁升级 (ReentrantReadWriteLock 不支持锁升级)在持有读锁情况下获得写锁会阻塞,要等待读锁释放
//        readLock.lock();
//        System.out.println("获得读锁");
//        writeLock.lock();
//        System.out.println("获得写锁");
//        readLock.unlock();
//        System.out.println("释放读锁");
//        writeLock.unlock();
//        System.out.println("获得释放写锁");
        //为了处理敏感数据才会使用锁降级
        ReentrantReadWriteLockDemo myCountDownLatch=new ReentrantReadWriteLockDemo();
        for(int i=0;i<5;i++){
            new Thread(()->{
                myCountDownLatch.doSomething();
            }).start();
        }
    }

    public void doSomething(){
        try{
            writeLock.lock();
            i++;
            writeLock.lock();
        }finally {
            writeLock.unlock();
        }
        try {
            //模拟复杂业务
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try{
            //如果每次更新后的数据都要查询
            //数据比较敏感可以使用所降级
            //writeLock.lock();
            System.out.println(i);
        }finally {
            writeLock.unlock();
        }
    }
}

ReentrantReadWriteLock 浅析

ReentrantReadWriteLock 什么情况下能获得锁

ReentrantReadWriteLock 有两把锁,一个是读锁一个是写锁
当A线程获得到写锁没有释放时,其他线程想获得读锁只能阻塞,然而这时A线程可以再次获得写锁和读锁。
当A线程获得到读锁没有释放时,其他线程也能获得读锁,这时A线程和其他线程想获得写锁都要阻塞。

ReentrantReadWriteLock 读锁和写锁重入次数计算

ReentrantReadWriteLock 类中有一个Sync静态内部类,Sync类中代码说明将一个int 32位的数拆分成两个无符号位的short类型,其中这个数的低16位表示写锁个数,高16位表示读锁个数,所以得出读锁或写锁的重入次数最大是65535
Sync代码如下

  abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 6317671515068378041L;
        /*
         * Read vs write count extraction constants and functions.
         * Lock state is logically divided into two unsigned shorts:
         * The lower one representing the exclusive (writer) lock hold count,
         * and the upper the shared (reader) hold count.
         */
        static final int SHARED_SHIFT   = 16;
        //1左移16位 是 65536  => 0000 0000 0000 0001 0000 0000 0000 0000
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        //1左移16位 是 65535 ⇒ 0000 0000 0000 0000 1111 1111 1111 1111
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        /** Returns the number of shared holds represented in count  */
        //无符号右移16位  返回读锁的个数  
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Returns the number of exclusive holds represented in count  */
        //int类型数据 与 0000 0000 0000 0000 1111 1111 1111 1111
        //返回写锁的个数
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
        }

ReentrantReadWriteLock 读锁上锁代码分析

简化代码执行流程,代码如下

 public void lock() {
      //获得读锁
      //这里传入的值为1,因为如果当前线程持有读锁这里重入次数要加1或得到锁重入次数初始化为1
      //这里会调用抽象类AbstractQueuedSynchronizer中的方法
      sync.acquireShared(1);
 }
 
 public final void acquireShared(int arg) {
 		//尝试去获得锁,如果没有获得到锁返回值会小于0
 		//调用ReentrantReadWriteLock中重写的方法
        if (tryAcquireShared(arg) < 0)
	        //当获得到锁没有获得到执行此方法
            doAcquireShared(arg);
 }

//来到ReentrantReadWriteLock中
protected final int tryAcquireShared(int unused) {
	/*
	 * Walkthrough:
	 * 1. If write lock held by another thread, fail.
	 * 2. Otherwise, this thread is eligible for
	 *    lock wrt state, so ask if it should block
	 *    because of queue policy. If not, try
	 *    to grant by CASing state and updating count.
	 *    Note that step does not check for reentrant
	 *    acquires, which is postponed to full version
	 *    to avoid having to check hold count in
	 *    the more typical non-reentrant case.
	 * 3. If step 2 fails either because thread
	 *    apparently not eligible or CAS fails or count
	 *    saturated, chain to version with full retry loop.
	 */
	Thread current = Thread.currentThread();
	//这个变量中存储着,读锁的重入次数和写锁的重入次数
	int c = getState();
	//如果有线程持有写锁并且不是自己持有的则直接返回-1 说明没有获得到锁
	if (exclusiveCount(c) != 0 &&
		getExclusiveOwnerThread() != current)
		return -1;
	//获得读锁个数
	int r = sharedCount(c);
	//判断是否应该阻塞,因为这里分析的是读锁,来到ReentrantReadWriteLock 的ReadLock内部类的readerShouldBlock方法中
	if (!readerShouldBlock() &&  
	    //判断重入次数是否小于65536
		r < MAX_COUNT &&
		//使用cas将读锁重入次数加1  
		//SHARED_UNIT是65536,因为读锁重入次数是高16位所以这里要加65536
		compareAndSetState(c, c + SHARED_UNIT)) {
		//读锁的重入次数为0,则本次获得读锁的是第一个
		if (r == 0) {
		    //将持有锁的线程设置成为当前线程
			firstReader = current;
			//设置读锁的重入次数为1
			firstReaderHoldCount = 1;
		} else if (firstReader == current) {
		    //如果已经是当前线程持有锁了,则直接重入次数+1
			firstReaderHoldCount++;
		} else {
		    //rh中存储着当前线程的id,和一个count
			HoldCounter rh = cachedHoldCounter;
			//如果当前变量没有缓存或 ThreadLocal中的线程id和当前线程的id不同
			//这里不能使用thread.getId() 是因为getId()方法不是final的
			if (rh == null || rh.tid != getThreadId(current))
			    //readHolds是会在ReentrantReadWriteLock 的内部类Sync中初始化(在Sync构造方法中初始化的)
			    //readHolds创建时候通过ThreadLocal将HoldCounter存到当前线程中
			    //HoldCounter中存了当先线程的pid和一个count
			    //获得到rh 然后给cachedHoldCounter 用来缓存
				cachedHoldCounter = rh = readHolds.get();
			//第一次获取HoldCounter的时候,rh的count肯定是0,获得了之后就会存到缓存中
			//意思是如果缓存中没有,count肯定是0
			else if (rh.count == 0)
				readHolds.set(rh);
			rh.count++;
		}
		return 1;
	}
	//重新循环去获得锁,但是如果重入次数超过65535则会抛出异常
	return fullTryAcquireShared(current);
}

//当获得到锁没有获得到执行此方法
private void doAcquireShared(int arg) {
		//Node.SHARED是一个空节点
		//创建一个节点,节点中存着当前线程,然后将这个节点通过cas添加到一个双向链表中
		//1 如果队列中没有一个节点,这是会将队列中的头结点和尾节点都设置添加的节点
		//2 如果队列中有节点,则通过cas将添加的节点设置成尾节点
		//返回添加到队列中的节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //找到当前节点的上一个节点
                final Node p = node.predecessor();
                //如果上一个节点是第一个节点
                if (p == head) {
	                //尝试去获得读锁
                    int r = tryAcquireShared(arg);
                    //获得到了读锁
                    if (r >= 0) {                    
                    	//将头节点设置成为自己,然后调用LockSupport.unpark(s.thread) 执行本线程
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //判断本节点和上一个节点的状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //在这里执行LockSupport.park(this); 阻塞当前线程
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
     } finally {
            //如果当前Node存储的线程还没有执行LockSupport.unpark(s.thread) 则执行cancelAcquire
            if (failed)
                cancelAcquire(node);
     }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值