a)Java中的锁——Lock和synchronized中介绍的ReentrantLock和synchronized基本上都是排它锁,意味着这些锁在同一时刻只容许一个线程进行访问,而读写锁在同一时刻能够容许多个读线程访问,在写线程访问的时候其余的读线程和写线程都会被阻塞。读写锁维护一对锁(读锁和写锁),经过锁的分离,使得并发性提升。html
b)关于读写锁的基本使用:在不使用读写锁的时候,通常状况下咱们须要使用synchronized搭配等待通知机制完成并发控制(写操做开始的时候,全部晚于写操做的读操做都会进入等待状态),只有写操做完成并通知后才会将等待的线程唤醒继续执行。并发
若是改用读写锁实现,只须要在读操做的时候获取读锁,写操做的时候获取写锁。当写锁被获取到的时候,后续操做(读写)都会被阻塞,只有在写锁释放以后才会执行后续操做。并发包中对ReadWriteLock接口的实现类是ReentrantReadWriteLock,这个实现类具备下面三个特色源码分析
①具备与ReentrantLock相似的公平锁和非公平锁的实现:默认的支持非公平锁,对于两者而言,非公平锁的吞吐量因为公平锁;post
②支持重入:读线程获取读锁以后可以再次获取读锁,写线程获取写锁以后能再次获取写锁,也能够获取读锁。ui
③锁能降级:遵循获取写锁、获取读锁在释放写锁的顺序,即写锁可以降级为读锁spa
1 public interface ReadWriteLock { 2 /** 3 * 返回读锁 4 */ 5 Lock readLock(); 6 7 /** 8 * 返回写锁 9 */ 10 Lock writeLock(); 11 }
①做为已经实现的同步组件,读写锁一样是须要实现同步器来实现同步功能,同步器的同步状态就是读写锁的读写状态,只是读写锁的同步器须要在同步状态上维护多个读线程和写线程的状态。使用按位切割的方式将一个整形变量按照高低16位切割成两个部分。对比下图,低位值表示当前获取写锁的线程重入两次,高位的值表示当前获取读锁的线程重入一次。读写锁的获取伴随着读写状态值的更新。当低位为0000_0000_0000_0000的时候表示写锁已经释放,当高位为0000_0000_0000_0000的时候表示读锁已经释放。线程
②从下面的划分获得:当state值不等于0的时候,若是写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取;若是写状态不等于0的话,读锁没有被获取。这个特色也在源码中实现。设计
①上面说到过,读写锁是支持重入的锁,而对于写锁而言仍是排他的,这样避免多个线程同时去修改临界资源致使程序出现错误。若是当前线程已经获取了写锁,则按照上面读写状态的设计增长写锁状态的值;若是当前线程在获取写锁的时候,读锁已经被获取或者该线程以前已经有别的线程获取到写锁,当前线程就会进入等待状态。3d
1 static final int SHARED_SHIFT = 16; 2 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //1左移16位减1=>0000_0000_0000_0000_1111_1111_1111_1111 3 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //返回读状态的值 4 protected final boolean tryAcquire(int acquires) { 5 /* 6 * Walkthrough: 7 * 1. 若是读状态不为0或者写状态不为0而且写线程不是本身,返回false 8 * 2. 若是已经超过了可重入的计数值MAX_COUNT,就会返回false 9 * 3. 若是该线程是可重入获取或队列策略容许,则该线程有资格得到锁定;同时更新全部者和写锁状态值 10 */ 11 Thread current = Thread.currentThread(); //获取当前线程 12 int c = getState(); //获取当前写锁状态值 13 int w = exclusiveCount(c); //获取写状态的值 14 //当同步状态state值不等于0的时候,若是写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取 15 if (c != 0) { 16 if (w == 0 || current != getExclusiveOwnerThread()) 17 return false; 18 if (w + exclusiveCount(acquires) > MAX_COUNT) //若是已经超过了可重入的计数值MAX_COUNT,就会返回false 19 throw new Error("Maximum lock count exceeded"); 20 // 重入锁:更新状态值 21 setState(c + acquires); 22 return true; 23 } 24 if (writerShouldBlock() || 25 !compareAndSetState(c, c + acquires)) 26 return false; 27 setExclusiveOwnerThread(current); 28 return true; 29 }
②分析一下上面的写锁获取源码code
tryAcquire中线程获取写锁的条件:读锁没有线程获取,写锁被获取而且被获取的线程是本身,那么该线程能够重入的获取锁,而判断读锁是否被获取的条件就是(当同步状态state值不等于0的时候,若是写状态(state & 0x0000FFFF)等于0的话,读状态是大于0的,表示读锁被获取)。对于读写锁而言,须要保证写锁的更新结果操做对读操做是可见的,这样的话写锁的获取就须要保证其余的读线程没有获取到读锁。
③写锁的释放源码
写锁的释放和ReentrantLock的锁释放思路基本相同,从源码中能够看出来,每次释放都是减小写状态,直到写状态值为0(exclusiveCount(nextc) == 0)的时候释放写锁,后续阻塞等待的读写线程能够继续竞争锁。
1 static final int SHARED_SHIFT = 16; 2 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //1左移16位减1=>0000_0000_0000_0000_1111_1111_1111_1111 3 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } //返回读状态的值 4 protected final boolean tryRelease(int releases) { 5 if (!isHeldExclusively()) 6 throw new IllegalMonitorStateException(); 7 int nextc = getState() - releases; 8 boolean free = exclusiveCount(nextc) == 0; //写状态值为0,就释放写锁,并将同步状态的线程持有者置为null,而后更新状态值 9 if (free) 10 setExclusiveOwnerThread(null); 11 setState(nextc); 12 return free; 13 }
①读锁是一样是支持重入的,除此以外也是共享式的,可以被多个线程获取。在同一时刻的竞争队列中,若是没有写线程想要获取读写锁,那么读锁总会被读线程获取到(而后更新读状态的值)。每一个读线程均可以重入的获取读锁,而对应的获取次数保存在本地线程中,由线程自身维护该值。
②获取读锁的条件:其余线程已经获取了写锁,则当前线程获取读锁会失败而进入等待状态;若是当前线程获取了写锁或者写锁没有被获取,那么就能够获取到读锁,并更细同步状态(读状态值)。
③读锁的每次释放都是减小读状态,
锁降级的概念:若是当先线程是写锁的持有者,并保持得到写锁的状态,同时又获取到读锁,而后释放写锁的过程。(注意不一样于这样的分段过程:当前线程拥有写锁,释放掉写锁以后再获取读锁的过程,这种分段过程不能称为锁降级)。
1 class CachedData { 2 Object data; 3 volatile boolean cacheValid; 4 final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 5 6 void processCachedData() { 7 // 获取读锁 8 rwl.readLock().lock(); 9 if (!cacheValid) { 10 // 在获取写锁以前必须释放读锁,不释放的话下面写锁会获取不成功,形成死锁 11 rwl.readLock().unlock(); 12 // 获取写锁 13 rwl.writeLock().lock(); 14 try { 15 // 从新检查state,由于在获取写锁以前其余线程可能已经获取写锁而且更改了state 16 if (!cacheValid) { 17 data = ... 18 cacheValid = true; 19 } 20 // 经过在释放写锁定以前获取读锁定来降级 21 // 这里再次获取读锁,若是不获取,那么当写锁释放后可能其余写线程再次得到写锁,致使下方`use(data)`时出现不一致的现象 22 // 这个操做就是降级 23 rwl.readLock().lock(); 24 } finally { 25 rwl.writeLock().unlock(); // 释放写锁,因为在释放以前读锁已经被获取,因此如今是读锁获取状态 26 } 27 } 28 29 try { 30 // 使用完后释放读锁 31 use(data); 32 } finally { 33 rwl.readLock().unlock(); //释放读锁 34 } 35 } 36 }}