目录
6.3 读写锁 ReentrantReadWriteLock 的原理
4.boolean tryLock(long timeout, TimeUnit unit)
4.boolean tryLock(long timeout, TimeUnit unit)
6.3 读写锁 ReentrantReadWriteLock 的原理
解决线程安全问题使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,某时只 有一个线程可以获取该锁,而实际中会有写少读多的场景,显然 ReentrantLock 满足不了 这个需求,所以 ReentrantReadWriteLock 应运而生。 ReentrantReadWriteLock 采用读写分 离的策略,允许多个线程可以同时获取读锁。
读写锁的内部维护了一个 ReadLock 和一个 WriteLock ,它们依赖 Sync 实现具体功能。 而 Sync 继承自 AQS,并且也提供了公平和非公平的实现。[这里说的是非公平锁],我们知道 AQS 中只维护了一个 state 状态,而 ReentrantReadWriteLock 则需要维护读状态和写状态,一个 state 怎么表示写和读两种状态呢 ?ReentrantReadWriteLock 巧妙地使用 state 的高 16 位表示读状态,也就是获取到读锁的次数 ;使用低 16 位表示获取到写锁的线程的可重入次数。
- firstReader 用来记录第一个获取到读锁的线程
- firstReaderHoldCount 则记录第一个获取到读锁的线程获取读锁的可重入次数
- cachedHoldCounter 用来记录最后一个获取读锁的线程获取读锁的可重入次数
- readHolds 是 ThreadLocal 变量,用来存放除去第一个获取读锁线程外的其他线程获取 读锁的可重入次数
static final int SHARED_SHIFT = 16; //共享锁(读锁)状态单位值65536 static final int SHARED_UNIT = (1 << SHARED_SHIFT); //共享锁线程最大个数65535 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //排它锁(写锁)掩码, 二进制 ,15个1 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; /** 返回读锁线程数 */ static int sharedCount(int c) { return c >>> SHARED_SHIFT; } /** 返回写锁可重入个数 */ static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } static final class HoldCounter { int count = 0; //线程id final long tid = getThreadId(Thread.currentThread()); } static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { public HoldCounter initialValue() { return new HoldCounter(); } }
写锁的获取与释放
1.void lock()
写锁是个独占锁,某时只有一个线程可以获取该锁。 如果当前没有线程获取到读锁和写锁 , 则当前线程可以获取到写锁然后返回。 如果当前已经有线程获取到读锁和写锁 , 则当前请求写锁的线程会被阻塞挂起。 另外,写锁是可重入锁,如果当前线程已经获取了该锁,再次获取只是简单地把可重入次数加 1 后直接返回。重写的tryAcquire方法
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
// sync重写的tryAcquire方法
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//tryAcquire方法
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
//(1) c!=0说明读锁或者写锁已经被某线程获取
if (c != 0) {
//(2)w=0说明已经有线程获取了读锁,w!=0并且当前线程不是写锁拥有者,则返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//(3)说明当前线程获取了写锁,判断可重入次数
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// (4)设置可重入次数(1)
setState(c + acquires);
return true;
}
// c == 0 则说明目前没有线程获取到读锁和写锁
//(5)第一个写线程获取写锁
if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
return false; //对于非公平锁来说总是返回 false
setExclusiveOwnerThread(current);
return true;
}
2.void lockInterruptibly()
类似于 lock() 方法,它的不同之处在于,它会对中断进行响应,也就是当其他线程调用了该线程的 interrupt() 方法中断了当前线程时,当前线程会抛出异常 InterruptedException 异常。public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
3.boolean tryLock()
尝试获取写锁,如果当前没有其他线程持有写锁或者读锁,则当前线程获取写锁会成功,然后返回 true 。 如果当前已经有其他线程持有写锁或者读锁则该方法直接返回 false ,且当前线程并不会被阻塞。 如果当前线程已经持有了该写锁则简单增加 AQS 的状态值后直接返回 true 。public boolean tryLock( ) { return sync.tryWriteLock(); } 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; }
4.boolean tryLock(long timeout, TimeUnit unit)
与 tryAcquire ()的不同之处在于,多了超时时间参数,如果尝试获取写锁失败则会把当前线程挂起指定时间,待超时时间到后当前线程被激活,如果还是没有获取到写锁则返回 false 。另外,该方法会对中断进行响应 , 也就是当其他线程调用了该线程的 interrupt() 方法中断了当前线程时,当前线程会抛出 InterruptedException 异常。public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); }
5.释放锁 void unlock()
尝试释放锁,如果当前线程持有该锁,调用该方法会让该线程对该线程持有的 AQS 状态值减 1 ,如果减去 1 后当前状态值为 0 则当前线程会释放该锁,否则仅仅减 1 而已。如果当前线程没有持有该锁而调用了该方法则会抛出 IllegalMonitorStateException 异常public void unlock() { sync.release(1); } public final boolean release(int arg) { //调用ReentrantReadWriteLock中sync实现的tryRelease方法 if (tryRelease(arg)) { //激活阻塞队列里面的一个线程 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } protected final boolean tryRelease(int releases) { //(6) 看是否是写锁拥有者调用的unlock if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //(7)获取可重入值,这里没有考虑高16位,因为获取写锁时读锁状态值肯定为0 int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; //(8)如果写锁可重入值为0则释放锁,否则只是简单地更新状态值 if (free) setExclusiveOwnerThread(null); setState(nextc); return free; }
读锁的获取与释放
1.void lock()
获取读锁,如果当前没有其他线程持有写锁,则当前线程可以获取读锁,AQS 的状态值 state 的高 16 位的值会增加 1,然后方法返回。否则如果其他一个线程持有写锁,则当前线程会被阻塞。
读锁的 lock 方法调用了 AQS 的 acquireShared 方法,在其内部调用了 ReentrantReadWriteLock 中的 sync 重写的 tryAcquireShared 方法public void lock() { sync.acquireShared(1); } public final void acquireShared(int arg) { //调用ReentrantReadWriteLock中的sync的tryAcquireShared方法 if (tryAcquireShared(arg) < 0) //调用AQS的doAcquireShared方法 doAcquireShared(arg); } protected final int tryAcquireShared(int unused) { //(1)获取当前状态值 Thread current = Thread.currentThread(); int c = getState(); //(2)判断是否写锁被占用 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; //(3)获取读锁计数 int r = sharedCount(c); //(4)尝试获取锁,多个读线程只有一个会成功,不成功的进入fullTryAcquireShared进行重试 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //(5)第一个线程获取读锁 if (r == 0) { firstReader = current; firstReaderHoldCount = 1; //(6)如果当前线程是第一个获取读锁的线程 } else if (firstReader == current) { firstReaderHoldCount++; } else { //(7)记录最后一个获取读锁的线程或记录其他线程读锁的可重入数 HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } //(8)类似tryAcquireShared,但是是自旋获取 return fullTryAcquireShared(current); }
标签:int,中锁,写锁,void,two,获取,读锁,线程,发包 From: https://blog.csdn.net/liiilbb/article/details/1443327522.void lockInterruptibly()
类似于 lock() 方法,不同之处在于,该方法会对中断进行响应,也就是当其他线程调用了该线程的interrupt() 方法中断了当前线程时,当前线程会抛出 InterruptedException 异常。3.boolean tryLock()
尝试获取读锁,如果当前没有其他线程持有写锁,则当前线程获取读锁会成功,然后返回 true 。如果当前已经有其他线程持有写锁则该方法直接返回 false ,但当前线程并不会被阻塞。 如果当前线程已经持有了该读锁则简单增加 AQS 的状态值高 16 位后直接返回 true 。其代码类似 tryLock 的代码4.boolean tryLock(long timeout, TimeUnit unit)
与 tryLock ()的不同之处在于,多了超时时间参数,如果尝试获取读锁失败则会把当前线程挂起指定时间,待超时时间到后当前线程被激活,如果此时还没有获取到读锁则返回 false 。另外,该方法对中断响应 , 也就是当其他线程调用了该线程的 interrupt() 方法中断了当前线程时,当前线程会抛出 InterruptedException 异常。5.void unlock()
释放锁的操作是委托给 Sync 类来做的,调用sync.releaseShared 方法
public void unlock() { sync.releaseShared(1); }