• ReentrantReadWriteLock源码分析(一)


    此处源码分析,主要是基于读锁,非公平机制,JDK1.8。

    问题:

    1、ReentrantReadWriteLock是如何创建读锁与写锁?

    2、读锁与写锁的区别是什么?

    3、锁的重入次数与获取锁的线程数分别是用哪种方式记录的?

    4、当队列中出现多个共享模式的线程节点连续排列时,那么当第一个共享模式的线程拿到锁之后,后面的共享线程节点怎么获取锁?

    一、创建ReadLock。

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

    1、当fair的值为false时,非公平的方式创建锁,当fair的值为true时,公平的方式创建锁。

    2、初始化readerLock与writerLock,这两个变量是ReentrantReadWriteLock的内部变量。

    3、sync执行非公平的锁。

    二、lock()源码分析

    2.1、sync.acquireShared(1)

    public void lock() {
    	sync.acquireShared(1);
    }
    
    public final void acquireShared(int arg) {
    	if (tryAcquireShared(arg) < 0)
    		doAcquireShared(arg);
    }
    

    (1)tryAcquireShared的作用是当前线程获取读锁,当返回1时,表示获取成功,-1表示获取失败。

    (2)doAcquireShared,表示获取失败的时候调用。将获取失败的线程加入到等待队列中,并调用LockSupport.park方法阻塞住,等待线程释放permit。

    2.2、tryAcquireShared(arg)

    protected final int tryAcquireShared(int unused) {
    
    	Thread current = Thread.currentThread();
    	// 获取到占有锁的线程数
    	int c = getState();
    	// 如果写锁被占领了且不是当前线程占领,那么直接返回 -1
    	if (exclusiveCount(c) != 0 &&
    		getExclusiveOwnerThread() != current)
    		return -1;
    	int r = sharedCount(c); // 占有共享锁的线程数
    	if (!readerShouldBlock() && // 如果队列的头节点的next节点是独享模式的线程节点即获取写锁的线程节点,返回true
    		r < MAX_COUNT && 	// 共享的数据不能超过65535
    		compareAndSetState(c, c + SHARED_UNIT)) {  // cas设置state
    		if (r == 0) {   // 线程来拿读锁,读锁和写锁没有被任何线程拥有,那么r==0
    			firstReader = current; // 
    			firstReaderHoldCount = 1;
    		} else if (firstReader == current) { // 如果线程重复获取读锁,那么从这里开始重入
    			firstReaderHoldCount++;
    		} else { // 如果读锁被线程x占领,线程y也要来申请读锁,那么分支就走到这里了
    
    		// HoldCounter类中存储了两个属性,一个是count,用于记录线程的重入次数,一个是tid,记录当前线程的id
    			HoldCounter rh = cachedHoldCounter;
    			
    			// 线程x拥有读锁之后,线程y第一次申请的时候会走到这里
    			//cachedHoldCounter 是一个缓存,保存当前操作线程的上一个线程的操作结果。线程y操作完之后,就会保存线程y的信息
    			// 如果另外一个线程z来获取到读锁的时候,虽然rh!=null,但是rh.tid != getThreadId(current),
    			//那么会创建一个默认的HoldCounter,并保存到cachedHoldCounter,并且默认的count=0
    			if (rh == null || rh.tid != getThreadId(current))
    			// readHolds.get(),查看源码可以知道,在这个方法中包含了数据初始化的过程,会调用ReentrantReadWriteLock.java
    			// 下面的方法
    			/**
    			* public HoldCounter initialValue() {
                   *             return new HoldCounter();
                     *       }
                     */
    				cachedHoldCounter = rh = readHolds.get(); 
    			else if (rh.count == 0) // 这个分支也会来到,当线程释放锁,但是没有关闭,当再次调用线程时,readHolds中会存在HoldCounter,count=0
    				readHolds.set(rh);
    			rh.count++; // 计算重入的次数
    		}
    		return 1;
    	}
    	return fullTryAcquireShared(current);
    }

    请注意:

    (1)ReentrantReadWriteLock中维持了一个类ThreadLocalHoldCounter,这个类会生成一个map,key是线程的id,value是HoldCounter对象,HoldCounter对象如下:

       static final class HoldCounter {
                int count = 0;
                // Use id, not reference, to avoid garbage retention
                final long tid = getThreadId(Thread.currentThread());
            }

    其中count就是线程的重入次数,tid就是当前线程的id。这个是与ReentrantLock区别的地方。

    (2)ReentrantReadWriteLock使用32位int类型来表示占有锁的线程数,其中高16位是获取到读锁的线程数,低16位是获取到写锁的线程数,提供了计算线程数的方法。

    static final int SHARED_SHIFT   = 16;(1)
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);(2)
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;(3)
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;(4)
    
    /** Returns the number of shared holds represented in count  */
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }(5)
    /** Returns the number of exclusive holds represented in count  */
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }(6)
    c + SHARED_UNIT

    其中(1)是共享移动常量;(2)是共享添加的常量;(3)是最大线程数65535(也就是11111111 11111111);(4)跟(3)一样;(5)计算共享线程数,把c的值向右移16为,并且高位补0; >>> 无符号右移,高位补0;(6)计算独享的线程数,把c的值与11111111 11111111 按位与,这样其实就是取到了写锁的线程数;(7)是共享线程+1。

    源码分析:

    2.2.1、readerShouldBlock()

    这个方法的作用是把判断当前获取读锁的线程是否需要阻塞,条件是:在等待队列中头节点的下一个节点是独享模式的线程。

    // 读锁应该被阻塞
    final boolean readerShouldBlock() {
    	return apparentlyFirstQueuedIsExclusive();
    }
    
    /**
     * Returns {@code true} if the apparent first queued thread, if one
     * exists, is waiting in exclusive mode.  If this method returns
     * {@code true}, and the current thread is attempting to acquire in
     * shared mode (that is, this method is invoked from {@link
     * #tryAcquireShared}) then it is guaranteed that the current thread
     * is not the first queued thread.  Used only as a heuristic in
     * ReentrantReadWriteLock.
     *如果第一个入队列的线程节点存在,并且工作在独享模式下,那么返回true;
     *如果这个方法返回true,并且当前线程以共享的模式获取锁,这个方法保证了它不是第一个入队列的
     *(读锁与读锁都是共存的,所以不会入队,只有当队列中有独享模式的线程节点的时候,获取共享模式的线程才会加入到队列中。)
     */
    final boolean apparentlyFirstQueuedIsExclusive() {
    	Node h, s;
    	// 头节点存在,并且存在下一个节点,下一个节点是独享模式,下一个节点的thread不是空,则返回true
    	return (h = head) != null &&
    		(s = h.next)  != null &&
    		!s.isShared()         &&
    		s.thread != null;
    }
    

    2.2.2、fullTryAcquireShared(current)

    这个方法的作用与tryAcquireShared的作用很类似。

    // 进入这个方法的条件,
    /**条件1:!readerShouldBlock() && // 如果第一个入队列的线程节点存在,并且工作在独享模式下,那么返回true;
    	*	条件2:r < MAX_COUNT && 	// 共享的数据不能超过65535,读锁的线程数已经超过了65535
    	*	条件3:compareAndSetState(c, c + SHARED_UNIT) // 两个竞争读锁的线程都运行到这里,第一个竞争成功,那么第二个就会竞争失败,返回false
        *  其实这个方法分别对这三种状态进行处理
       */
       /**
             * Full version of acquire for reads, that handles CAS misses
             * and reentrant reads not dealt with in tryAcquireShared.
             */
            final int fullTryAcquireShared(Thread current) {
                /*
                 * This code is in part redundant with that in
                 * tryAcquireShared but is simpler overall by not
                 * complicating tryAcquireShared with interactions between
                 * retries and lazily reading hold counts.
                 */
                HoldCounter rh = null;
                for (;;) {
                    int c = getState();
                    // 如果排他锁被别的线程拿了,直接返回-1
                    if (exclusiveCount(c) != 0) {
                        if (getExclusiveOwnerThread() != current)
                            return -1;
                        // else we hold the exclusive lock; blocking here
                        // would cause deadlock.
                    } else if (readerShouldBlock()) {  // 这里是对条件1的处理
                    // 如果队列的头的下一个节点是请求的排他锁的线程在等待,那么就返回true
                        // Make sure we're not acquiring read lock reentrantly
                        if (firstReader == current) {
                            // assert firstReaderHoldCount > 0;
                        } else {
                            if (rh == null) {
                                rh = cachedHoldCounter;
                                if (rh == null || rh.tid != getThreadId(current)) {
                                    rh = readHolds.get();
                                    // 如果当前线程的count==0,也就是说当前线程才进来,没有获取到锁,那么直接把它从readHolds中移除
                                    if (rh.count == 0)
                                    // 移除当前线程的HoldCounter
                                        readHolds.remove();
                                }
                            }
                            // 移除之后,返回-1
                            if (rh.count == 0)
                                return -1;
                        }
                    }
                    // 这里是对条件2的处理,直接抛出错误!
                    if (sharedCount(c) == MAX_COUNT)
                        throw new Error("Maximum lock count exceeded");
                        // 这里是对条件3的处理,竞争设置state,如果竞争还是失败,那么就要再循环一次,直到死循环能够跳出去
                    if (compareAndSetState(c, c + SHARED_UNIT)) {
                    // 如果共享锁的数量为0
                        if (sharedCount(c) == 0) {
                        // 设置第一个线程为当前的线程
                            firstReader = current;
                            // 设置HoldCount =1
                            firstReaderHoldCount = 1;
                        } else if (firstReader == current) {
                            firstReaderHoldCount++;
                        } else {
                            if (rh == null)
                                rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current))
                                rh = readHolds.get();
                            else if (rh.count == 0)
                                readHolds.set(rh);
                            rh.count++;
                            cachedHoldCounter = rh; // cache for release
                        }
                        return 1;
                    }
                }
            }
    

      

     

      

  • 相关阅读:
    [转]Nginx配置信息详解
    [转]浅谈Nginx负载均衡和F5的区别
    [转]MySQL中datetime和timestamp的区别及使用
    理解MyCat分库分表
    理解秒杀系统
    [转]设计模式之桥接模式
    [转]MySQL查询语句执行过程详解
    两步完美解决 androud 模拟器太慢的问题
    android hook 框架 xposed 如何实现挂钩
    android hook 框架 xposed 如何实现注入
  • 原文地址:https://www.cnblogs.com/boywwj/p/8649837.html
Copyright © 2020-2023  润新知