高并发学习笔记(七)
三、同步器的实现(三)
4. ReentrantReadWriteLock
ReentrantLock的底层是实现了AQS同步器的独占模式,接下来学习一个AQS同步器共享模式的实现ReentrantReadWriteLock。由前面的学习我们知道ReentrantLock是独占锁,也就是说不论线程之间是读/读、读/写或是写/写操作,都需要先获取到锁才能执行,这回带来一定的性能上的损耗,因为读操作不会带来并发问题的,因此要将读/读操作和读/写、写/写操作区别对待,以达到性能上的提高。为此,引入了ReentrantReadWriteLock,他的特点是:一个资源可以同时被多个只读线程访问,或者一个只写线程访问,但读写操作不能同时进行(若读写可以同时进行,必然会出现脏读/幻读/不可重复读等现象)。
ReentrantReadWriteLock内部维护着两个锁,一个读锁,一个写锁。读锁可以同时被多个线程获取,而写锁同一时刻只能被一个线程获取,并且一旦写锁被使用,其他所有读线程也将被阻塞。由此我们可以知道,读锁应该是共享锁,写锁则是独占锁。下面先来看看ReentrantReadWriteLock的继承关系:
有继承关系图可知:ReentrantReadWriteLock实现了ReadWriteLock接口,并且维护着5个内部类ReadLock,WriteLock,Sync,FairSync,NonfairSync。其中FairSync和NonfairSync是Sync的子类,与ReentrantLock类似,是公平锁和非公平锁的底层实现,同时Sync内部也维护着两个内部类ThreadLocalHoldCounter(ThreadLocal的子类)和HoldCounter(读线程的计数类)。
了解了ReentrantReadWriteLock的基本构成,接下来看个用法示例:
/** * ReentrantReadWriteLock使用示例 * Created by bzhang on 2019/3/20. */ public class TestReentrantReadWriteLock { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private ReadLock readLock = lock.readLock(); private WriteLock writeLock= lock.writeLock(); //展品存放容器 private volatile List<String> list = new ArrayList<>(); //展示展品 public void show(int index){ while (true){ readLock.lock(); try { if (index>list.size()-1){ //没有新展品就休眠 System.out.println(Thread.currentThread().getName()+"展品还没更新,洗洗睡去"); TimeUnit.SECONDS.sleep(1); }else { //有新展品就展示 System.out.println(Thread.currentThread().getName()+"现在展览的是:"+list.get(index)); break; } } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); } } } //更新展品 public void update(String data){ writeLock.lock(); try { list.add(data); }finally { writeLock.unlock(); } } public static void main(String[] args) { TestReentrantReadWriteLock test = new TestReentrantReadWriteLock(); //生产新展品的线程,每2秒生产1一个新展品 Thread thread = new Thread(new Runnable() { @Override public void run() { int i = 0; while (true) { String data = "展品" + ++i; System.out.println(Thread.currentThread().getName()+"更新了" + data); test.update(data); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } }); thread.start(); //3个展品展示线程,有新展品就展出,没有新展品会休眠1秒再次询问,若还是没有在休眠,直到有新展品为止 for (int k = 0;k < 3;k++){ new Thread(new Runnable() { @Override public void run() { for (int t = 0;;t++){ test.show(t); } } }).start(); } } }
看完例子,我们再来逐个分析下ReentrantReadWriteLock的基本构成,先看看ReentrantReadWriteLock的父接口ReadWriteLock:
//ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。 //只要没有 writer,读取锁可以由多个 reader 线程同时保持。换言之,写入锁是独占的。 public interface ReadWriteLock { //返回一个用于读操作的锁 Lock readLock(); //返回一个用于写操作的锁 Lock writeLock(); }
上面的接口展示了ReentrantReadWriteLock要实现的功能,即要实现两个锁,一个读,一个写。我们先看ReentrantReadWriteLock的构造方法以接口方法的实现。看看这两个锁是如何创建的。
//ReentrantReadWriteLock的构造方法及对接口ReadWriteLock的实现 public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { //读锁,由内部类创建提供的,用以实现写操作的独占 private final ReentrantReadWriteLock.ReadLock readerLock; //写锁,由内部类创建提供,用以实现读操作的共享 private final ReentrantReadWriteLock.WriteLock writerLock; //同步队列,同样也是内部类提供 final Sync sync; //无参构造,创建一个非公平的ReentrantReadWriteLock public ReentrantReadWriteLock() { this(false); } //依据fair创建一个公平或非公平的ReentrantReadWriteLock。 //同时初始化属性readerLock和writerLock 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; } }
由构造方法可知,ReentrantReadWriteLock内部实例化了一个同步队列和一个读锁,一个写锁。下面先来看看读锁的源码:
//读锁的实现 public static class ReadLock implements Lock, java.io.Serializable { //读锁中的同步队列引用 private final Sync sync; //构造方法,可以看出同步队列就是直接使用ReentrantReadWriteLock中同步队列 //因为读锁初始化时是直接lock=this的(readerLock = new ReadLock(this)); protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } //读锁中获取锁的方法,调用了acquireShared方法,说明同步队列实现了共享模式。 public void lock() { sync.acquireShared(1); } //获取读取锁,除非当前线程被中断 public void lockInterruptibly() throws InterruptedException { sync.acquireSharedInterruptibly(1); } //尝试获取锁,仅尝试一次,失败就直接返回false public boolean tryLock() { return sync.tryReadLock(); } //在一定时间内尝试获取锁 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } //解锁 public void unlock() { sync.releaseShared(1); } //返回条件队列,可以看出读取锁不支持条件队列 public Condition newCondition() { throw new UnsupportedOperationException(); } }
知道了读取锁实现了哪些功能,下面来看4个读取锁的获取方法:
//以获取读取锁,不可中断的lock方法为入口,看看读取锁的获取过程 public void lock() { sync.acquireShared(1); //实际尝试获取锁的方法 } //AQS中的方法,这里重点要看tryAcquireShared的实现,doAcquireShared已在AQS中分析过,不在重复 public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } //AQS中方法 final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; //同步队列头结点h不为null且头结点的后继结点s也不为null且s结点不为共享模式且s结点中的线程不为null //满足上述条件即可认定所有读取锁要阻塞等待,即头结点后的第一个结点不是共享模式即需要阻塞 //因为头结点的后继结点就是下个要获取锁的线程,该线程是独占模式就表名是写入锁,则需要阻塞读取锁 return (h = head) != null && (s = h.next) != null && !s.isShared() && s.thread != null; } //非公平队列 static final class NonfairSync extends Sync { //拥有写入锁的线程是否应该阻塞,恒为false final boolean writerShouldBlock() { return false; } //判断所有拥有读取锁线程是否应该阻塞 final boolean readerShouldBlock() { //下个线程将要获取的是什么锁,获取读取锁就不需要阻塞,否则要阻塞 return apparentlyFirstQueuedIsExclusive(); } } //公平队列 static final class FairSync extends Sync { //判断前面是否还有等待的线程,有的话那么当前线程也应该等待 final boolean writerShouldBlock() { return hasQueuedPredecessors(); //当前线程是否是同步等待队列的第一个等待结点 } //判断前面是否还有等待的线程,有的话那么当前线程也应该等待 final boolean readerShouldBlock() { return hasQueuedPredecessors(); } } //AQS的子类,真正实现读写锁同步的类 abstract static class Sync extends AbstractQueuedSynchronizer { //sync中重写的tryAcquireShared protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); //获取当前线程 int c = getState(); //sync同步队列的同步状态 //判断持有锁的线程持有的是读取锁还是写入锁,且持有写入锁的线程是否是当前线程 //若是写入锁(state低位不为0,必是写入锁)且持有写入锁的不是当前线程(不是重入锁),直接返回获取锁失败 if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current) return -1; //获取持有读取锁的线程数 int r = sharedCount(c); //readerShouldBlock判断读取锁是否需要阻塞 //r < MAX_COUNT想要获取读取锁的线程数不能大于最大值65535 //compareAndSetState表示更新同步状态成功 //同时满足上述三个条件表示成功获取到读取锁 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //判断是不是没有线程获取到读取锁过 if (r == 0) { //若当前线程是第一个获取读锁的线程,将其赋予变量firstReader, //并使firstReaderHoldCount=1,表示当前线程获取到读锁次数为1 //由此可见第一个获取到读锁的线程,不会为其分配一个技计数对象HoldCounter用于记录获取读锁的次数 //而是直接存放在全局变量中,作用可能是为了提高效率吧(不确定) firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { //获取读锁的线程是firstReader,则直接计数+1 firstReaderHoldCount++; } else { //若获取到读锁的不是firstReader,那么为其创建一个 HoldCounter rh = cachedHoldCounter; //获取当前线程缓存的计数对象 //判断rh是否为null,或当前线程与缓存中的线程是否是同一个 //若rh为空或线程不对应,则重新从线程容器中取 if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); //从线程容器ThreadLocal中获取HoldCounter else if (rh.count == 0) //rh不为null,但重入计数为0,则要更新容器中的HoldCounter对象 readHolds.set(rh); rh.count++; //重入计数+1 } return 1; } return fullTryAcquireShared(current); //获取读锁失败,执行该方法,不断尝试获取读锁 } //计算写入锁是否有被独占 //Sync中state状态被分割成两部分,用以分别表示读取锁和写入锁的状态 //state是个int类型,4个字节32位,其中高16位表示持有读取锁的线程数(sharedCount)。 //写入锁为低16位,表示写锁的重入次数 (exclusiveCount)。 //状态值为 0 表示锁空闲,sharedCount不为 0 表示分配了读锁,exclusiveCount 不为 0 表示分配了写锁 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; //返回低位的值 } //持有读取锁的线程数 static int sharedCount(int c){ return c >>> SHARED_SHIFT; //返回高位的值 } static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); //65536 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //拥有读取锁的线程数最大值65535 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //用于获取状态值的低16位,65535 private transient ThreadLocalHoldCounter readHolds; //每个线程私有的容器,存放HoldCounter private transient HoldCounter cachedHoldCounter; //记录每个线程获取读锁的次数的(即重入次数) private transient Thread firstReader = null; //第一个获取读取锁的线程 private transient int firstReaderHoldCount; //第一个获取读锁的线程获取的次数 //每个线程获取读锁次数的计数类 static final class HoldCounter { int count = 0; final long tid = getThreadId(Thread.currentThread()); //与线程id绑定 } //每个线程私有的容器,用于存放计数 static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { public HoldCounter initialValue() { return new HoldCounter(); } } //尝试获取读锁 final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { //自旋循环 int c = getState(); //获取同步状态 //判断当前使用的是写入锁还是读取锁 if (exclusiveCount(c) != 0) { //判断当前拥有写入锁的线程是不是当前线程 //若不是直接返回获取读锁失败 //若是,则表明当前线程要降锁级,即原来获取的是写入锁,现在重入后要执行的是读操作,要获取读锁 if (getExclusiveOwnerThread() != current) return -1; } else if (readerShouldBlock()) { //若是写入锁空闲,且读取锁应该被阻塞,这个条件只有在公平锁下才会进入 //判断当前线程是不是第一次获取读锁的线程 if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); //获取与当前线程对应的HoldCounter对象 if (rh.count == 0) //若计数为0,表示没有获取到读锁 readHolds.remove(); //删除计数对象 } } if (rh.count == 0) //计数为0,返回获取锁失败 return -1; } } if (sharedCount(c) == MAX_COUNT) //判断拥有读取锁的线程数有没有超标 throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { //CAS更新同步状态,更新失败继续循环更新知道成功为止 //判断读取锁是不是第一次被获取 if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { //若当前线程是第一次获取读取锁的线程 firstReaderHoldCount++; //更新重入计数+1 } 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; } } } Sync() { readHolds = new ThreadLocalHoldCounter(); //创建一个线程私有的容器 setState(getState()); } //拥有读取锁的线程是否应该阻塞,有子类实现 abstract boolean readerShouldBlock(); //拥有写入锁的线程是否应该阻塞,有子类实现 abstract boolean writerShouldBlock(); //判断当前线程是否是拥有写入锁的线程 protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } }
//获取读取锁,除非当前线程被中断 public void lockInterruptibly() throws InterruptedException { sync.acquireSharedInterruptibly(1); } //AQS中的方法 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) //和lock方法一样 doAcquireSharedInterruptibly(arg); //AQS中已分析过,不在深入 }
//尝试获取锁,仅尝试一次,失败就直接返回false public boolean tryLock() { return sync.tryReadLock(); } //sync中的尝试获取读锁方法 //尝试获取读锁不会只尝试一次(与ReentrantLock中不同,ReentrantLock中只有尝试一次) //只要写锁没被使用,尝试获取读锁一定要获取到,因为读锁是共享锁,不像独占锁只能一个拥有 final boolean tryReadLock() { Thread current = Thread.currentThread(); for (;;) { //自旋等待 获取读锁 int c = getState(); //获取同步状态 //判断当前是读锁还是写锁,若是写锁是不是重入锁 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return false; //若是写锁但不是重入锁,直接返回尝试获取读锁失败 int r = sharedCount(c); //获取拥有读锁的线程数 if (r == MAX_COUNT) //拥有读锁的线程是否超标 throw new Error("Maximum lock count exceeded"); //CAS更新状态,更新成功表示获取读锁成功,反之继续循环尝试 if (compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } 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 true; } } }
//在一定时间内尝试获取锁 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } //AQS中的方法 public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquireShared(arg) >= 0 || //和lock方法一样的调用一样,参考lock doAcquireSharedNanos(arg, nanosTimeout); //在一定时间内尝试获取锁,AQS中已分析过,不在深入 }
读锁的释放:
//读锁的释放 public void unlock() { sync.releaseShared(1); } //AQS中释放共享锁的方法 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //判断释放锁是否成功 doReleaseShared(); //唤醒后继结点区尝试获取锁 return true; } return false; } //sync中重写的尝试释放锁方法 protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); //获取当前线程的引用 //修改将要释放读锁的参数,即释放锁时若锁有重入(HoldCount>1),则先将锁的重入次数HoldCount-1 //若锁没有重入了(HoldCount=1)时,释放锁要将线程私有容器中的计数器移除 //判断当前线程是不是第一个获取读锁的线程(第一个获取读锁的线程没有使用线程私有容器,HoldCount存放在sync中,要单独处理) if (firstReader == current) { // assert firstReaderHoldCount > 0; //判断firstReader是不是重入锁,若是重入锁,则重入次数-1 //若不是则令firstReader为null if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; //判断缓存的HoldCount与当前线程对应的HoldCount是否是同一个 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; if (compareAndSetState(c, nextc)) return nextc == 0; } }
接下来再看看写锁实现了哪些功能:
public static class WriteLock implements Lock, java.io.Serializable { //写锁的内部实现还是通过同步队列来实现 private final Sync sync; //与外部类使用同步一个AQS对象 protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } //获取写入锁,不可中断 public void lock() { sync.acquire(1); } //获取写入锁,但可被中断 public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } //尝试获取写入锁 public boolean tryLock( ) { return sync.tryWriteLock(); } //在一定时间内尝试获取锁 public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } //释放写入锁 public void unlock() { sync.release(1); } //获取条件队列,可以看出写入锁是独占锁模式,支持Condition public Condition newCondition() { return sync.newCondition(); } //判断当前线程是否是持有写入锁的线程 public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } //查看当前线程保持写入锁的数量,没有持有锁则返回0 public int getHoldCount() { return sync.getWriteHoldCount(); } }
同样,我们以获取锁的过程来看看写入锁的源码:
//获取写入锁,不可中断 public void lock() { sync.acquire(1); //真正去获取写入锁的方法 } //AQS中的方法 public final void acquire(int arg) { //tryAcquire尝试获取锁,有子类重写 //acquireQueued加入同步队列等待获取锁,AQS中已分析过,不在展开 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); //恢复中断标志 } //Sync中重写的方法,尝试获取写入锁 protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); //获取当前线程引用 int c = getState(); //获取同步状态 int w = exclusiveCount(c); //查看当前使用的是读锁还是写锁 //判断是否有锁被使用,c==0表示读锁与写锁均没有被线程拥有,处于释放状态 if (c != 0) { //判断当前使用的读锁还是写锁,若是写锁则当前线程是不是拥有写锁的线程 //w==0表示当前使用的读锁,那么获取写锁就不可能成功,直接返回失败 //若是使用写锁,但持有写锁的不是当前线程(即不是重入锁),那么获取也是不会成功的 if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) //判断写锁重入次数有没有超过最大值 throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); //更新同步状态(即重入次数) return true; } //writerShouldBlock判断写锁是否应该被阻塞等待 //在公平锁下,要根据当前线程在同步队列中是否是第一位置(FIFO原则) //在非公平锁下,恒为不要阻塞,允许抢占 //在需要阻塞写锁或者修改同步状态失败的情况下,说明获取写锁失败 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; //当不需要阻塞且修改同步状态成功,说明获取写锁成功,将持有写锁的线程改为当前线程 setExclusiveOwnerThread(current); return true; }
可以看出WriteLock中写锁的获取方法tryAcquire与ReentrantLock中tryAcquire基本相同,仅多了一个判断读锁是否存在。而WriteLock中另外三个获取锁的方法:lockInterruptibly()和tryLock(timeout,timeunit)的逻辑与lock基本相同,不在多说,tryLock()也仅有些许区别:
//尝试获取写入锁 public boolean tryLock( ) { return sync.tryWriteLock(); } //尝试获取写锁,与读锁中的一直尝试获取不一样,写锁的尝试锁因为是独占模式的关系,只尝试一次 //可以看出与tryAcquire的逻辑基本相同。 final boolean tryWriteLock() { Thread current = Thread.currentThread(); int c = getState(); 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"); } if (!compareAndSetState(c, c + 1)) return false; setExclusiveOwnerThread(current); return true; }
再来看看写锁的释放:
//释放写入锁 public void unlock() { sync.release(1); } //AQS中独占模式下的释放方法,调用子类重写的tryRelease方法 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } //AQS子类Sync中重写的tryRelease protected final boolean tryRelease(int releases) { //判断当前线程是否是持有写入锁的线程,若不是,直接释放失败,抛异常 //说明写入锁要先持有才能释放 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; //更新同步状态 //更新后的同步状态是完全释放写锁,还是只是重入次数减少的标识 //同时标识还表示写锁释放是否成功 boolean free = exclusiveCount(nextc) == 0; //若是完全释放锁,则将持有写锁的线程设为null if (free) setExclusiveOwnerThread(null); setState(nextc); //否则就更新同状态 return free; }
由上面的额分析我们知道WriteLock是独占锁,支持condition功能的,其实现用的还是AQS中的ConditionObject,没有变化,在此就不在重复。
最后总结一下,ReentrantReadWriteLock有如下的性质:
1.获取顺序
a.非公平模式(默认)
当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。
b.公平模式
当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。当有写线程持有写锁或者有等待的写线程时,一个尝试获取公平的读锁(非重入)的线程就会阻塞。这个线程直到等待时间最长的写锁获得锁后并释放掉锁后才能获取到读锁。
2.可重入
允许读锁可写锁可重入。写锁可以获得读锁,读锁不能获得写锁。
3.锁降级
允许写锁降低为读锁
4.中断锁的获取
在读锁和写锁的获取过程中支持中断
5.支持Condition
写锁提供Condition实现
6.监控
提供确定锁是否被持有等辅助方法
- Go语言并发与并行学习笔记(一)
- 学习笔记(九)并发(三)
- Go语言并发与并行学习笔记(一)
- Java并发学习笔记(八)-LinkedBlockingQueue
- Go语言学习笔记-并发
- 并发编程学习笔记之Lock与synchronized
- Java并发学习笔记(15)信号量(Semaphore) 关卡((2)CyclicBarrier)
- 学习笔记:java并发编程学习之初识Concurrent
- JAVA并发编程学习笔记之ReentrantLock (r)
- MySQL学习笔记之四:并发控制和事务机制
- Java并发编程学习笔记 深入理解volatile关键字的作用
- 学习JAVA多线程编程 --- 《JAVA多线程编程核心技术》第2章 对象及变量的并发访问 笔记
- WCF学习笔记之并发与限流
- Java并发读书学习笔记(九)——性能与可伸缩性
- Java并发读书学习笔记(十)——显式锁
- Java学习笔记—多线程(并发工具类)
- Java并发读书学习笔记(十一)——原子变量与非阻塞同步机制
- 并发编程实战学习笔记(七)——避免活跃性问题
- 并发编程实战学习笔记(八)——性能与可伸缩性
- 并发学习笔记(三):join与wait/notify