湖北建设工程造价协会网站,简单建站,用那个程序做网站收录好,iis 5 新建网站目录 1、ReentrantReadWriteLock 入门1.1、概念1.2、案例1.2.1、写写互斥1.2.2 锁降级 2、ReentrantReadWriteLock 源码解析2.1、属性2.2、构造方法2.3、内部类2.4、读写状态的设计 —— 按位切割使用2.5、【写锁】加锁方法 lock() —— ReentrantReadWriteLock.WriteLock2.4.1… 目录 1、ReentrantReadWriteLock 入门1.1、概念1.2、案例1.2.1、写写互斥1.2.2 锁降级 2、ReentrantReadWriteLock 源码解析2.1、属性2.2、构造方法2.3、内部类2.4、读写状态的设计 —— 按位切割使用2.5、【写锁】加锁方法 lock() —— ReentrantReadWriteLock.WriteLock2.4.1、acquire() 方法 —— AQS2.4.1.1、tryAcquire() 方法 —— AQS由 ReentrantReadWriteLock.Sync 实现2.4.1.1.1 writerShouldBlock() 方法 —— ReentrantReadWriteLock.Sync由子类实现 2.6、【写锁】解锁方法 unlock() —— ReentrantReadWriteLock.WriteLock2.6.1、release() 方法 —— AQS2.6.1.1、tryRelease() 方法 —— AQS由 ReentrantReadWriteLock.Sync 实现 2.7、【读锁】加锁方法 lock() —— ReentrantReadWriteLock.ReadLock2.7.1、acquireShared() 方法 —— AQS2.7.1.1、tryAcquireShared() 方法 —— AQS由 ReentrantReadWriteLock.Sync 实现2.7.1.1.1、readerShouldBlock() 方法 —— ReentrantReadWriteLock.Sync由子类实现2.7.1.1.1.1、apparentlyFirstQueuedIsExclusive() 方法 —— AQS 2.7.1.1.2、fullTryAcquireShared() 方法 —— ReentrantReadWriteLock.Sync 2.8、【读锁】解锁方法 unlock() —— ReentrantReadWriteLock.ReadLock 1、ReentrantReadWriteLock 入门
1.1、概念
ReentrantReadWriteLock读写锁。它表示两个锁一个是读操作相关的锁称为共享锁一个是写相关的锁称为排他锁。适用于读多写少的场景如果用独占锁效率及其低下提高并发性能但也需要更多的内存和处理器时间来维护状态信息 在没有写操作的情况下多个线程同时读一个资源没有任何问题所以应该允许多个线程同时读取共享资源但是如果一个线程想去写这些共享资源就不应该允许其他线程对该资源进行读和写的操作了 并发读读互斥读写、写读、写写 ReentrantLock 与 ReentrantReadWriteLock 区别
ReentrantLock互斥锁它允许同一线程对共享资源进行重入即该线程在获得锁后可以再次获得该锁而不被阻塞ReentrantReadWriteLock由一个读锁和一个写锁组成它允许多个线程同时读取共享资源但只允许一个线程写入共享资源
当前线程获取读锁的条件
没有其它线程的写锁没有写请求或者有写请求但当前线程和持有锁的线程是同一个线程
当前线程获取写锁的条件
没有其它线程的读锁没有其它线程的写锁
三个重要的特性
公平选择性支持非公平默认和公平的锁获取方式吞吐量还是非公平优于公平可重入读锁和写锁都支持线程重入。读线程获取读锁后能够再次获取读锁但是不能获取写锁写线程在获取写锁之后能够再次获取写锁同时也可以获取读锁锁降级线程获取写锁之后获取读锁再释放写锁这样实现了写锁变为读锁也叫锁降级
1.2、案例
1.2.1、写写互斥
public class Test {private static MapString, Object map new HashMap();private static ReentrantReadWriteLock lock new ReentrantReadWriteLock();public static void put(String key, Object value) throws InterruptedException {System.out.println(开始---- Thread.currentThread().getName());lock.writeLock().lock();try {System.out.println(执行------ Thread.currentThread().getName());map.put(key, value);Thread.sleep(1000);} finally {lock.writeLock().unlock();System.out.println(结束------ Thread.currentThread().getName());}}public static Object get(String key) {System.out.println(开始---- Thread.currentThread().getName());lock.readLock().lock();try {System.out.println(执行------ Thread.currentThread().getName());return map.get(key);} finally {lock.readLock().unlock();System.out.println(执行------ Thread.currentThread().getName());}}public static void main(String[] args) throws InterruptedException {Runnable writeTask () - {try {put(1, 1);} catch (InterruptedException e) {throw new RuntimeException(e);}};new Thread(writeTask).start();new Thread(writeTask).start();}
}执行结果
开始----Thread-1
开始----Thread-0
执行------Thread-1
结束------Thread-1
执行------Thread-0
结束------Thread-0根据结果可以线程 1、0 同时执行但线程 1 获取写锁后线程 0 阻塞直至线程 1 执行完释放写锁之后线程 0 获取写锁执行最后释放写锁 当然读者也可以去测试 读写、读读 等。 1.2.2 锁降级
锁降级 先获取写锁然后再获取读锁最后释放写锁的过程。在这个过程中线程可以先访问共享资源然后放弃写权限转而访问读资源【可以避免写操作期间读操作的阻塞提高并发性能由写锁降为读锁释放写锁后仍然持有读锁】。
还是上述那个例子
public static void put(String key, Object value) {lock.writeLock().lock();try {map.put(key, value);// 获取读锁lock.readLock().lock();} finally {lock.writeLock().unlock();// 此处如果不释放读锁其他线程获取写锁时将被阻塞//lock.readLock().unlock();}
}2、ReentrantReadWriteLock 源码解析
2.1、属性
public class ReentrantReadWriteLock implements ReadWriteLock {// 读锁private final ReentrantReadWriteLock.ReadLock readerLock;// 写锁private final ReentrantReadWriteLock.WriteLock writerLock;// 同步机制final Sync sync;
}2.2、构造方法
public ReentrantReadWriteLock() {this(false);
}public ReentrantReadWriteLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync();readerLock new ReadLock(this);writerLock new WriteLock(this);
}默认非公平机制
2.3、内部类
// AQS分公平、非公平
abstract static class Sync extends AbstractQueuedSynchronizer {// 尝试获取锁protected final boolean tryAcquire(int acquires) {}// 尝试释放锁protected final boolean tryRelease(int releases) {}protected final int tryAcquireShared(int unused) {}// 写锁是否应该阻塞abstract boolean writerShouldBlock();abstract boolean readerShouldBlock();// 每个读线程持有的计数Sync 构造函数中初始化读线程计数为 0 时删除private transient ThreadLocalHoldCounter readHolds;// 缓存最后一个读锁的计数private transient HoldCounter cachedHoldCounter;// 记录第一个读锁private transient Thread firstReader null;// 第一个读锁的计数器private transient int firstReaderHoldCount;// 内部类继承了 ThreadLocal和当前线程绑定static final class ThreadLocalHoldCounter extends ThreadLocalHoldCounter {public HoldCounter initialValue() {return new HoldCounter();}}// 内部类读线程的计数器static final class HoldCounter {int count 0;final long tid getThreadId(Thread.currentThread());}
}// 非公平
static final class NonfairSync extends Sync {final boolean writerShouldBlock() {return false;}
}// 公平
static final class FairSync extends Sync {final boolean writerShouldBlock() {return hasQueuedPredecessors();}
}// 读锁
public static class ReadLock implements Lock {
}// 写锁
public static class WriteLock implements Lock {private final Sync sync;// 加锁通过 Sync#acquire() 方法加锁public void lock() {}
}2.4、读写状态的设计 —— 按位切割使用
在分析 ReentrantLock 的时候Sync 内部类类是继承于 AQS以 int state 为线程加锁状态state 0 表示未加锁state 0 表示已加锁
同样ReentrantReadWriteLock 也是继承于 AQS 来实现同步那 int state 是如何同时来区分读锁和写锁的呢 如果要用一个变量维护多种状态需要采用 “按位切割使用” 的方式来维护这个变量将其切分为两部分高 16 为表示读低 16 为表示写 查看 Sync 类中的静态变量
abstract static class Sync extends AbstractQueuedSynchronizer {// 高16位为读锁低16位为写锁static final int SHARED_SHIFT 16;// 读锁单位static final int SHARED_UNIT (1 SHARED_SHIFT);// 读锁最大数量static final int MAX_COUNT (1 SHARED_SHIFT) - 1;// 写锁最大数量static final int EXCLUSIVE_MASK (1 SHARED_SHIFT) - 1;// 获取读锁的数量【高 16 位移位】static int sharedCount(int c) { return c SHARED_SHIFT; }// 获取写锁的数量【 与运算】static int exclusiveCount(int c) { return c EXCLUSIVE_MASK; }}移位操作符在二进制下进行移位【十进制转化为二进制】 左移。如果左移过程中超过了 32 位高位就会舍弃低位补零【最终结果十进制数 * (2 ^ n)】。如3 2 3 * (2 ^ 2) 12右移。低位会舍弃高位补零【最终结果十进制数 / (2 ^ n)】右移零填充运算符【用来执行无符号位移的位运算符】将一个数的二进制表示向右移动并用 0 填充左侧的空位 所以通过移位操作符 state【高 16读低 16写】00000000 00000000 00000000 00000000SHARED_UNIT 00000000 00000001 00000000 00000000MAX_COUNT 00000000 00000000 11111111 11111111EXCLUSIVE_MASK 00000000 00000000 11111111 11111111 2.5、【写锁】加锁方法 lock() —— ReentrantReadWriteLock.WriteLock
public void lock() {sync.acquire(1);
}调用 AQS#acquire() 方法【Sync 没有重写此方法父类有】
2.4.1、acquire() 方法 —— AQS
public final void acquire(int arg) {if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {selfInterrupt();}
}acquire() 方法在之前的文章 六ReentrantLock —— 可重入锁 中已经分析过只有 tryAcquire() 方法供子类实现。这里实现的子类是 ReentrantReadWriteLock.Sync
2.4.1.1、tryAcquire() 方法 —— AQS由 ReentrantReadWriteLock.Sync 实现
// 尝试获取写锁
protected final boolean tryAcquire(int acquires) { Thread current Thread.currentThread();// 获取 state 的值int c getState();// 获取写锁的数量int w exclusiveCount(c);if (c ! 0) {// 要么就是读锁要么就是写锁// 1.如果没有写锁【有读锁】不管是否当前线程是持有锁线程直接返回【持有读锁线程无法再持有写锁读写互斥】// 2.如果是写锁但是不是当前持有锁线程直接返回【写写互斥】if (w 0 || current ! getExclusiveOwnerThread()) {return false;}// 是写锁且是当前线程if (w exclusiveCount(acquires) MAX_COUNT) {throw new Error(Maximum lock count exceeded);}// 重入setState(c acquires);return true;}// 没有读锁、写锁竞争写锁if (writerShouldBlock() || !compareAndSetState(c, c acquires)) {return false;}setExclusiveOwnerThread(current);return true;
}2.4.1.1.1 writerShouldBlock() 方法 —— ReentrantReadWriteLock.Sync由子类实现
分公平锁、非公平锁
公平锁由 AQS#hasQueuedPredecessors() 方法决定【同步等待队列中有线程等待返回 true则不竞争锁直接返回 false否则竞争写锁进行 CAS 操作返回 true】非公平锁直接抢占进行 CAS 操作返回 true
2.6、【写锁】解锁方法 unlock() —— ReentrantReadWriteLock.WriteLock
public void unlock() {sync.release(1);
}调用 AQS#release() 方法【Sync 没有重写此方法父类有】
2.6.1、release() 方法 —— AQS
public final boolean release(int arg) {if (tryRelease(arg)) {Node h head;if (h ! null h.waitStatus ! 0)unparkSuccessor(h);return true;}return false;
}tryRelease() 方法被 Sync 类实现
2.6.1.1、tryRelease() 方法 —— AQS由 ReentrantReadWriteLock.Sync 实现
protected final boolean tryRelease(int releases) {if (!isHeldExclusively()) {// 如果当前线程不是持有锁线程则抛异常throw new IllegalMonitorStateException();} int nextc getState() - releases;// boolean free (exclusiveCount(nextc) 0);// 判断写锁数量是否为 0【重入】boolean free exclusiveCount(nextc) 0;if (free) {setExclusiveOwnerThread(null);}setState(nextc);return free;
}2.7、【读锁】加锁方法 lock() —— ReentrantReadWriteLock.ReadLock
public void lock() {sync.acquireShared(1);
}调用 AQS#acquireShared() 方法
2.7.1、acquireShared() 方法 —— AQS
public final void acquireShared(int arg) {if (tryAcquireShared(arg) 0) {doAcquireShared(arg);}
}2.7.1.1、tryAcquireShared() 方法 —— AQS由 ReentrantReadWriteLock.Sync 实现
protected final int tryAcquireShared(int unused) {Thread current Thread.currentThread();int c getState();if (exclusiveCount(c) ! 0 getExclusiveOwnerThread() ! current) {// 如果有写锁且当前线程不是持有写锁线程则返回 -1【写读互斥】return -1;}int r sharedCount(c);// 如果获取到读锁则返回 1// 1.readerShouldBlock() 方法// 公平锁有线程排队返回 true直接跳过否则返回 false// 非公平锁为防止写线程饥饿问题所以判断 head 节点的后驱节点是否为写锁如果是返回 true直接跳过否则返回 false// 2.compareAndSetState(c, c SHARED_UNIT)CAS 操作 state 的高 16 位读锁if (!readerShouldBlock() r MAX_COUNT compareAndSetState(c, c SHARED_UNIT)) {if (r 0) {// 如果之前没有读锁则这是第一个线程获取读锁计数器记为 1且用 firstReader 执向firstReader current;firstReaderHoldCount 1;} else if (firstReader current) {// 如果之前有读锁且当前线程等于 firstReader第一个获取读锁的线程则自加firstReaderHoldCount;} else {// 缓存最后一个读线程的计数HoldCounter rh cachedHoldCounter;// 我是第 2 个拿到读锁的rh null| 我是第 NN 2个拿到读锁的且当前线程不是最后一个线程那么将 cachedHoldCounter 设置为最后一个线程if (rh null || rh.tid ! getThreadId(current)) {cachedHoldCounter rh readHolds.get();} else if (rh.count 0) {// 我是第 NN 2个拿到读锁的当前线程是最后一个线程且计数器为 0 【读锁释放】readHolds.set(rh);}// 计数器自增rh.count;}return 1;}// 用于 CAS 操作失败【N N 2个线程同时执行 CAS 操作】return fullTryAcquireShared(current);
}如果写锁被其它线程持有则获取读锁失败返回 -1判断是否因同步队列策略而阻塞【公平锁有线程排队跳过逻辑否则执行逻辑非公平锁为防止写线程饥饿问题所以判断 head 节点的后驱节点是否为写锁如果是跳过逻辑否则执行逻辑】。如果不阻塞就执行 CAS 操作更新 state如果步骤 2 失败因为线程显然不符合条件【CAS 失败或计数饱和】则使用完整的重试循环链接到版本
2.7.1.1.1、readerShouldBlock() 方法 —— ReentrantReadWriteLock.Sync由子类实现
公平锁由 hasQueuedPredecessors() 方法决定【同步等待队列中有线程等待返回 true则不竞争锁直接返回 false否则竞争写锁进行 CAS 操作返回 true】
final boolean readerShouldBlock() {return hasQueuedPredecessors();
}非公平锁由 apparentlyFirstQueuedIsExclusive() 方法决定【如果同步等待队列中不为空且 head 节点的后继节点为【独占式】节点则返回 true否则返回 false如果有读锁写锁是无法获取的防止写线程饥饿一直阻塞读锁是共享的可以一直获取】
final boolean readerShouldBlock() {return apparentlyFirstQueuedIsExclusive();
}2.7.1.1.1.1、apparentlyFirstQueuedIsExclusive() 方法 —— AQS
final boolean apparentlyFirstQueuedIsExclusive() {Node h, s;return (h head) ! null (s h.next) ! null !s.isShared() s.thread ! null;
}如果同步等待队列中不为空且 head 节点的后继节点为【独占式】节点则返回 true否则返回 false
2.7.1.1.2、fullTryAcquireShared() 方法 —— ReentrantReadWriteLock.Sync
final int fullTryAcquireShared(Thread current) {HoldCounter rh null;// 自旋for (;;) {int c getState();// 是否存在写锁if (exclusiveCount(c) ! 0) {if (getExclusiveOwnerThread() ! current) {// 如果存在写锁且当前线程不是持有写锁的线程返回 -1如果是就会去持有读锁return -1;}// 公平有排队的进入逻辑没排队的过// 非公平head 的 next 是写不是进入逻辑如果不是过} else if (readerShouldBlock()) {if (firstReader current) {// assert firstReaderHoldCount 0;} else {// 需要阻塞去掉最后一个计数为 0 的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;}return 1;}}
}2.8、【读锁】解锁方法 unlock() —— ReentrantReadWriteLock.ReadLock
protected final boolean tryReleaseShared(int unused) {Thread current Thread.currentThread();if (firstReader current) {if (firstReaderHoldCount 1)firstReader null;elsefirstReaderHoldCount--;} 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;if (compareAndSetState(c, nextc))return nextc 0;}
}判断当前线程是否是第一个获取读锁的线程。如果是它将检查读锁的持有次数如果持有次数为1则将第一个读者置为null否则将持有次数减1如果当前线程不是第一个获取读锁的线程则会尝试从缓存中获取HoldCounter对象该对象记录了线程持有的读锁次数。如果缓存中的HoldCounter对象与当前线程不匹配则从readHolds中获取与当前线程对应的HoldCounter对象。接着它将该对象的持有次数减1并在线程持有次数不大于1时从readHolds中移除该对象。如果持有次数小于等于0会抛出一个异常最后该函数进入一个循环不断尝试将锁的状态减去SHARED_UNIT表示读锁的单位并使用compareAndSetState方法原子地更新锁的状态。如果更新成功它会检查更新后的状态是否为0如果是则返回true表示成功释放了读锁