【搞定Java并发编程】第20篇:读写锁 --- ReentrantReadWriteLock详解

上一篇:重入锁 --- ReentrantLock 详解(点击查看)

本文目录

1、读写锁的概述

2、读写锁的具体实现

2.1、读写状态的设计

2.2、锁的获取

2.2.1、写锁的获取

2.2.2、读锁的获取

2.3、锁的释放

2.3.1、写锁的释放

2.3.2、读锁的释放

3、ReentrantReadWriteLock中的其他方法

3.1、getOwner()

扫描二维码关注公众号,回复: 4710187 查看本文章

3.2、getReadLockCount()

3.3、getReadHoldCount()

3.4、getWriteHoldCount()

4、总结


推荐几篇不错的文章:

1、轻松掌握java读写锁(ReentrantReadWriteLock)的实现原理:这篇文章用图解式的方式说明了读写锁的两种模式下的工作流程(公平读写锁和非公平读写锁),内容简单易懂,推荐阅读。

2、深入理解读写锁—ReadWriteLock源码分析分析了读锁和写锁的获取、读锁和写锁的释放的源码。

1、读写锁的概述

之前提到的锁(如:ReentrantLock)基本都是排他锁,即在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。

读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大的提升。

一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。Java并发包中提供读写锁的实现是ReentrantReadWriteLock,它提供的特性如下表所示:

 但是事实上在ReentrantReadWriteLock里锁的实现是靠内部java.util.concurrent.locks.ReentrantReadWriteLock.Sync完成的。这个类看起来比较眼熟,它是AQS的一个子类,这中类似的结构在CountDownLatch、ReentrantLock、Semaphore里面都存在。

在ReentrantReadWriteLock里面的锁主体就是一个Sync,也就是FairSync或者NonfairSync,所以说实际上只有一个 
锁,只是在获取读取锁和写入锁的方式上不一样而已。

先看下ReadWriteLock接口的源码:

public interface ReadWriteLock {
    
    // 读锁
    Lock readLock();

    // 写锁
    Lock writeLock();
}

ReentrantReadWriteLock类是ReadWriteLock的实现类,它的整体结果如下:

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    
    private final ReentrantReadWriteLock.ReadLock readerLock;
   
    private final ReentrantReadWriteLock.WriteLock writerLock;
   
    final Sync sync;

    public ReentrantReadWriteLock() {
        this(false);   // 默认是非公平锁
    }

    // 公平锁和非公平锁在ReentrReentrantReadWriteLock初始化的时候就确定了
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    // 实现父接口ReadWriteLock接口中的方法
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    // 静态内部类Sync,它的父类是AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
		// ...
        // 当别的线程获取读锁时,是否要阻塞
        abstract boolean readerShouldBlock();

        // 当别的线程获取写锁时,是否要阻塞
        abstract boolean writerShouldBlock();
        // ...
    }
	
    // 静态内部类NonfairSync,其父类是Sync
    static final class NonfairSync extends Sync {
		// ...
    }
	
    // 静态内部类FairSync,其父类是Sync
    static final class FairSync extends Sync {
		// ...
    }
	
    // 读锁,实现了Lock接口
    public static class ReadLock implements Lock, java.io.Serializable {
        
        private final Sync sync;
        // 构造器
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
		// ...
    }
	
    // 写锁,实现了Lock接口
    public static class WriteLock implements Lock, java.io.Serializable {
       
        private final Sync sync;
        // 构造器 
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
		// ...
    }

	// ...
}

从上面的的代码中可以发现,Sync中提供了很多方法,但是只有两个方法是抽象的,也就是需要子类实现的。那我们就来看看它的子类FairSync和NonFairSync是如何实现这两个抽象方法的。

  • FairSync
static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
}

writerShouldBlock和readerShouldBlock方法都表示当有别的线程也在尝试获取锁时,是否应该阻塞。 

对于公平模式,hasQueuedPredecessors()方法表示前面是否有等待线程。一旦前面有等待线程,那么为了遵循公平,当前线程也就应该被挂起。 

  • NonFairSync
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
}

从上面可以看到,非公平模式下,writerShouldBlock直接返回false,说明不需要阻塞;而readShouldBlock调用了apparentlyFirstQueuedIsExcluisve()方法。该方法在当前线程是写锁占用的线程时,返回true;否则返回false。也就说明,如果当前有一个写线程正在写,那么该读线程应该阻塞。

  • ReentrantReadWriteLock有如下特性: 
  • 1、获取顺序 

非公平模式(默认): 

当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。 

公平模式 :

当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。 

当有写线程持有写锁或者有等待的写线程时,一个尝试获取公平的读锁(非重入)的线程就会阻塞。这个线程直到等待时间最长的写锁获得锁后并释放掉锁后才能获取到读锁。 

  • 2、可重入 

允许读锁可写锁可重入。写锁可以获得读锁,读锁不能获得写锁。 

  • 3、锁降级 

允许写锁降低为读锁 ,但不支持读锁升级为写锁。

  • 4、中断锁的获取 

在读锁和写锁的获取过程中支持中断。

  • 5、支持Condition 

写锁提供Condition实现。

  • 6、监控 

提供确定锁是否被持有等辅助方法


2、读写锁的具体实现

下面分析ReentrantReadWriteLock的具体实现,主要包括:读写状态的设计、写锁的获取与释放、读锁的获取与释放以及锁降级。至于公平性与非公平性,在上篇文章重入锁ReentrantLock中已经讲解过了,本质上都是一样的,这里不再单独做区分的讲解了。

2.1、读写状态的设计

读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态。回想ReentrantLock中自定义同步器的实现,同步状态表示锁被一个线程重复获取的次数。而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。

如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写,划分方式如下图所示:

读写锁状态的划分方式

上图所示的当前同步状态表示一个线程已经获取了写锁,期重进入了2次,同时也连续获取了2次读锁。读锁和写锁通过位运算确定自己读和写的状态。

2.2、锁的获取

2.2.1、写锁的获取

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

  • 第1步:调用内部类WriteLock中的lock()方法
public static class WriteLock implements Lock, java.io.Serializable {
    // ...
            
    // 获取写锁
    public void lock() {
        sync.acquire(1);  // 调用的是同步器AQS中的方法
    }
    // ...
}
  • 第2步:调用AQS中的acquire方法(因为sync继承了AQS类,所有可以调用AQS中开放的方法)
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
            selfInterrupt();
        }         
}
  • 第3步:调用Sync中的tryAcquire方法
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    
	// ...
	
    // 静态内部类Sync,它的父类是AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
		 // ...
        protected final boolean tryAcquire(int acquires) {
            // 获取调用lock方法的当前线程
            Thread current = Thread.currentThread();
            // 获取当前线程的状态
            int c = getState();
            // 获取写锁的状态,写锁是排它锁
            int w = exclusiveCount(c);
            if (c != 0) {
                // 存在读锁或者当前获取线程不是已经获取写锁的线程,返回false
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                // 如果写锁的个数超过了最大值65535,抛出异常
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 写入重入锁,返回true
                setState(c + acquires);
                return true;
            }
            // 如果当前没有线程获得锁,如果写线程应该被阻塞或者CAS失败,返回false
            if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
                return false;
            // 否则将当前线程置为获得写锁的线程,返回true
            setExclusiveOwnerThread(current);
            return true;
        }
		// ...
    }
}

从代码和注释可以看到,获取写锁时有三步: 

1. 如果当前有写锁或者读锁。如果只有读锁,返回false,因为这时如果可以写,那么读线程得到的数据就有可能错误;如果有写锁,但是线程不同,即不符合写锁重入规则,返回false ;

2. 如果写锁的数量将会超过最大值65535,抛出异常;否则,写锁重入 ;

3. 如果没有读锁或写锁的话,如果需要阻塞或者CAS失败,返回false;否则将当前线程置为获得写锁的线程。

从上面可以看到调用了writerShouldBlock方法,FairSync的实现是如果等待队列中有等待线程,则返回false,说明公平模式下,只要队列中有线程在等待,那么后来的这个线程也是需要记入队列等待的;NonfairSync中的直接返回的直接是false,说明不需要阻塞。从上面的代码可以得出,当没有锁时,如果使用的非公平模式下的写锁的话,那么返回false,直接通过CAS就可以获得写锁。

2.2.2、读锁的获取

读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问时,读锁总是能够成功地被获取到,而所做的也只是增加读状态。

如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已经被其他线程获取了,则进入等待状态。

  • 第1步:调用调用内部类Re'a'dLock中的lock()方法
public static class ReadLock implements Lock, java.io.Serializable {
    // ...
            
    // 获取读锁
    public void lock() {
        sync.acquireShared(1);  // 调用的是同步器AQS中的方法
    }
    // ...
}
  • 第2步:调用AQS中共享模式:acquireShared方法
if (tryAcquireShared(arg) < 0)
    doAcquireShared(arg);

当tryAcquireShared()方法小于0时,那么会执行doAcquireShared方法将该线程加入到等待队列中。 
Sync实现了tryAcquireShared方法,如下:

  • 第3步:调用Sync中的tryAcquireShared方法
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    
	// ...
	
    // 静态内部类Sync,它的父类是AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // ...
        
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            // 如果当前有写线程并且本线程不是写线程,不符合重入,失败
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // 得到读锁的个数
            int r = sharedCount(c);
            // 如果读不应该阻塞并且读锁的个数小于最大值65535,并且可以成功更新状态值,成功
            if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
                // 如果当前读锁为0
                if (r == 0) {
                    // 第一个读线程就是当前线程
                    firstReader = current;
                    firstReaderHoldCount = 1;
                }
                // 如果当前线程重入了,记录firstReaderHoldCount
                else if (firstReader == current) {
                    firstReaderHoldCount++;
                }
                // 当前读线程和第一个读线程不同,记录每一个线程读的次数
                else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            // 否则,循环尝试
            return fullTryAcquireShared(current);
        }
        
        // ...
    }
}

从上面的代码以及注释可以看到,分为三步: 

1. 如果当前有写线程并且本线程不是写线程,那么失败,返回 -1;

2. 否则,说明当前没有写线程或者本线程就是写线程(可重入),接下来判断是否应该读线程阻塞并且读锁的个数是否小于最小值,并且CAS成功使读锁+1,成功,返回1。其余的操作主要是用于计数的; 

3. 如果2中失败了,失败的原因有三,第一是应该读线程应该阻塞;第二是因为读锁达到了上线;第三是因为CAS失败,有其他线程在并发更新state,那么会调动fullTryAcquireShared方法。

fullTryAcquiredShared方法如下:

final int fullTryAcquireShared(Thread current) {

    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        // 一旦有别的线程获得了写锁,返回-1,失败
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        } 
        // 如果读线程需要阻塞
        else if (readerShouldBlock()) {
            // 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();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        // 如果读锁达到了最大值,抛出异常
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 如果成功更改状态,成功返回
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                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;
        }
    }
}

从上面可以看到fullTryAcquireShared与tryAcquireShared有很多类似的地方。 在上面可以看到多次调用了readerShouldBlock方法,对于公平锁,只要队列中有线程在等待,那么将会返回true,也就意味着读线程需要阻塞;对于非公平锁,如果当前有线程获取了写锁,则返回true。一旦不阻塞,那么读线程将会有机会获得读锁。

总结下获取锁:

1、如果当前没有写锁或读锁时,第一个获取锁的线程都会成功,无论该锁是写锁还是读锁;

2、如果当前已经有了读锁,那么这时获取写锁将失败,获取读锁有可能成功也有可能失败;

3、如果当前已经有了写锁,那么这时获取读锁或写锁,如果线程相同(可重入),那么成功;否则失败。

2.3、锁的释放

获取锁要做的是更改AQS的状态值以及将需要等待的线程放入到队列中。释放锁要做的就是更改AQS的状态值以及唤醒队列中的等待线程来继续获取锁。

2.3.1、写锁的释放

  • 第1步:调用WriteLock类中的unlock方法
public void unlock() {
    sync.release(1);
}
  • 第2步:通过Sync调用AQS中的relaease方法
 public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  • 第3步:调用Sync中的tryRelease方法

一旦释放成功了,那么如果等待队列中有线程再等待,那么调用unparkSuccessor将下一个线程解除挂起。 
Sync需要实现tryRelease方法。具体实现源码如下所示:

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    
	// ...
	
    // 静态内部类Sync,它的父类是AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // ...
        
        protected final boolean tryRelease(int releases) {
            // 如果没有线程持有写锁,但是仍要释放,抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            // 如果没有写锁了,那么将AQS的线程置为null
            if (free)
                setExclusiveOwnerThread(null);
            // 更新状态
            setState(nextc);
            return free;
        }
        
        // ...
    }
}

从上面可以看到,写锁的释放主要有三步: 

1. 如果当前没有线程持有写锁,但是还要释放写锁,抛出异常;

2. 得到解除一把写锁后的状态,如果没有写锁了,那么将AQS的线程置为null;

3. 不管第二步中是否需要将AQS的线程置为null,AQS的状态总是要更新的。

  • 从上面可以看到,返回true当且只当没有写锁的情况下,还有写锁则返回false。

2.3.2、读锁的释放

  • 第1步:调用ReadLock中的unlock方法
public void unlock() {
    sync.releaseShared(1);
}
  • 第2步:通过Sync调用AQS的releaseShared方法
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
  • 第3步:调用Sync的tryReleaseShared方法,如果释放成功,调用doReleaseShared尝试唤醒下一个节点
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    
	// ...
	
    // 静态内部类Sync,它的父类是AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // ...
        
        protected final boolean tryReleaseShared(int unused) {
            // 得到调用unlock的线程
            Thread current = Thread.currentThread();
            // 如果是第一个获得读锁的线程
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            }
            // 否则,是HoldCounter中计数-1
            else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            // 死循环
            for (;;) {
                int c = getState();
                // 释放一把读锁
                int nextc = c - SHARED_UNIT;
                // 如果CAS更新状态成功,返回读锁是否等于0;失败的话,则重试
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
        
        // ...
    }
}

从上面可以看到,释放锁的第一步是更新firstReader或HoldCounter的计数,接下来进入死循环,尝试更新AQS的状态,一旦更新成功,则返回;否则,则重试。 

释放读锁对读线程没有影响,但是可能会使等待的写线程解除挂起开始运行。所以,一旦没有锁了,就返回true,否则false;返回true后,那么则需要释放等待队列中的线程,这时读线程和写线程都有可能再获得锁。

释放锁的总结:

1、如果当前是写锁被占有了,只有当写锁的数据降为0时才认为释放成功;否则失败。因为只要有写锁,那么除了占有写锁的那个线程,其他线程即不可以获得读锁,也不能获得写锁; 

2、如果当前是读锁被占有了,那么只有在写锁的个数为0时才认为释放成功。因为一旦有写锁,别的任何线程都不应该再获得读锁了,除了获得写锁的那个线程。


3、ReentrantReadWriteLock中的其他方法

3.1、getOwner()

getOwner方法用于返回当前获得写锁的线程,如果没有线程占有写锁,那么返回null。实现如下:

protected Thread getOwner() {
    return sync.getOwner();
}

可以看到直接调用了Sync的getOwner方法,下面是Sync的getOwner方法:

final Thread getOwner() {
    // Must read state before owner to ensure memory consistency
    return ((exclusiveCount(getState()) == 0) ? null : getExclusiveOwnerThread());
}

如果独占锁的个数为0,说明没有线程占有写锁,那么返回null;否则返回占有写锁的线程。

3.2、getReadLockCount()

getReadLockCount()方法用于返回读锁的个数,实现如下:

public int getReadLockCount() {
    return sync.getReadLockCount();
}

可以看到调用了Sync中的getReadLockCount()方法:

final int getReadLockCount() {
    return sharedCount(getState());
}

static int sharedCount(int c) {
    return c >>> SHARED_SHIFT; 
}

从上面代码可以看出,要想得到读锁的个数,就是看AQS的state的高16位。这和前面讲过的一样,高16位表示读锁的个数,低16位表示写锁的个数。

3.3、getReadHoldCount()

getReadHoldCount()方法用于返回当前线程所持有的读锁的个数,如果当前线程没有持有读锁,则返回0。

public int getReadHoldCount() {
    return sync.getReadHoldCount();
}

可以看到,调用了Sync中的getReadHoldCount()方法:

final int getReadHoldCount() {
    // 如果没有读锁,自然每个线程都是返回0
    if (getReadLockCount() == 0)
        return 0;

    // 得到当前线程
    Thread current = Thread.currentThread();
    // 如果当前线程是第一个读线程,返回firstReaderHoldCount参数
    if (firstReader == current)
        return firstReaderHoldCount;
    // 如果当前线程不是第一个读线程,得到HoldCounter,返回其中的count
    HoldCounter rh = cachedHoldCounter;
    // 如果缓存的HoldCounter不为null并且是当前线程的HoldCounter,直接返回count
    if (rh != null && rh.tid == getThreadId(current))
        return rh.count;

    // 如果缓存的HoldCounter不是当前线程的HoldCounter,
    // 那么从ThreadLocal中得到本线程的HoldCounter,返回计数         
    int count = readHolds.get().count;
    // 如果本线程持有的读锁为0,从ThreadLocal中移除
    if (count == 0) readHolds.remove();
    return count;
}

从上面的代码中,可以看到两个熟悉的变量,firstReader和HoldCounter类型。这两个变量在读锁的获取中接触过,前面没有细说,这里细说一下。HoldCounter类的实现如下:

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

readHolds是ThreadLocalHoldCounter类,定义如下:

static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
	public HoldCounter initialValue() {
	    return new HoldCounter();
	}
}

可以看到,readHolds存储了每一个线程的HoldCounter,而HoldCounter中的count变量就是用来记录线程获得的写锁的个数。所以可以得出结论:Sync维持总的读锁的个数,在state的高16位;由于读线程可以同时存在,所以每个线程还保存了获得的读锁的个数,这个是通过HoldCounter来保存的。 

除此之外,对于第一个读线程有特殊的处理,Sync中有如下两个变量:

private transient Thread firstReader = null;
private transient int firstReaderHoldCount;

firstReader表示第一个得到读锁的线程,firstReaderHoldCount表示这个线程获得的写锁。所以可以得出结论:第一个获取到读锁的信息保存在firstReader中;其余获取到读锁的线程的信息保存在HoldCounter中。 

看完了HoldCounter和firstReader,再来看一下getReadLockCount的实现,主要有三步: 

1. 当前没有读锁,那么自然每一个线程获得的读锁都是0; 

2. 如果当前线程是第一个获取到读锁的线程,那么返回firstReadHoldCount; 

3. 如果当前线程不是第一个获取到读锁的线程,得到该线程的HoldCounter,然后返回其count字段。如果count字段为0,说明该线程没有占有读锁,那么从readHolds中移除。获取HoldCounter分为两步,第一步是与cachedHoldCounter比较,如果不是,则从readHolds中获取。

3.4、getWriteHoldCount()

getWriteHoldCount()方法返回当前线程所持有写锁的个数。

public int getWriteHoldCount() {
    return sync.getWriteHoldCount();
}

接着调用了Sync中的getWriteHoldCount()方法:

final int getWriteHoldCount() {
    return isHeldExclusively() ? exclusiveCount(getState()) : 0;
}

可以看到如果没有线程持有写锁,那么返回0;否则返回AQS的state的低16位。


4、总结

当分析ReentranctReadWriteLock时,或者说分析内部使用AQS实现的工具类时,需要明白的就是AQS的 state 代表的是什么。ReentrantLockReadWriteLock中的 state 同时表示写锁和读锁的个数。为了实现这种功能,state的高16位表示读锁的个数,低16位表示写锁的个数。

AQS有两种模式:共享模式和独占模式,读写锁的实现中,读锁使用共享模式;写锁使用独占模式;

另外一点需要记住的即使,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁。


上一篇:重入锁 --- ReentrantLock 详解(点击查看)

猜你喜欢

转载自blog.csdn.net/pcwl1206/article/details/85012834