需要判断同步队列中是否还有其他线程在挂起等待,如存在应该按照入队顺序获取锁
final boolean writerShouldBlock() { return hasQueuedPredecessors(); } public final boolean hasQueuedPredecessors() { //1.获取同步队列头,尾节点 Node t = tail; Node h = head; Node s; // h !=t 同步队列不为空 // 队列中还有其他线程在等待锁,则返回true return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } 3.2、释放写锁unlock方法释放锁
public void unlock() { sync.release(1); }可见,调用内部类Sync的release方法,Sync继承AQS
public final boolean release(int arg) { if (tryRelease(arg)) { //1,释放锁成功 Node h = head; if (h != null && h.waitStatus != 0) //2.唤醒同步队列中等待线程 unparkSuccessor(h); return true; } return false; }核心在尝试释放锁方法上,看看写锁的释放锁方法tryRelease
protected final boolean tryRelease(int releases) { //1,判断当前线程是否持有当前锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //2,同步状态 - 需要释放的写锁同步值 int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; if (free) //3,free ==true,完全释放写锁,将当前获取独占锁线程置空 setExclusiveOwnerThread(null); //4,更新state值 setState(nextc); return free; }注: 在释放写锁占用次数时, state的高16的读锁有值也不影响,减去releases,首先减去的state低位的数,而且在释放写锁时,state的低16位的值一定>=1,不存在减少读锁的值情况。
int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0;也可改写为如下面代码
//1,获取state值 int c = getState(); //2,获取写锁的值 int w= exclusiveCount(c); int remain = w- releases; boolean free = remain== 0; 4、读锁 4.1、获取读锁读锁调用lock方法加锁,实际调用Sync的acquireShared方法
public void lock() { sync.acquireShared(1); }走进acquireShared,获取共享锁方法
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }尝试获取锁tryAcquireShared,如果返回值<0, 表示获取读锁失败
主要执行步骤:
1、首先判断是否存在其他线程在占用写锁,有需要挂起等待;
2、在不用阻塞等待,且读锁值没有超过最大值,cas更新成功了state的值,可以获取到读锁,还会做以下事:
a. 第一个获取读锁的,直接记录线程对象和其重入获取读锁的次数
b. 非第一个获取读锁的,则获取缓存计数器(cachedHoldCounter),其记录上一次获取读锁的线程,如果是同一个线程,则直接更新其计数器的重入次数,如果缓存计数器为空或缓存计数器的线程不是当前获取读锁的线程,则从当前线程本地变量中获取自己的计数器,更新计数器的值
protected final int tryAcquireShared(int unused) { //1,获取当前线程对象 Thread current = Thread.currentThread(); //2,获取同步锁的值 int c = getState(); /*3,exclusiveCount(c) != 0 计算写锁的同步状态,不等于0,说明有写锁已经获取到同步锁, *需要判断当前线程是否等于获取写锁线程, *是,可以允许再次获取读锁,这里涉及到锁降级问题,写锁可以降为读锁 *否则不让获取,写读互斥 */ if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; //4,获取读锁同步状态 int r = sharedCount(c); /** *此处3个判断条件 * 1.是否应该阻塞等待,这里也是基于公平锁和非公平获取锁实现 * 2.读锁同步状态值是超过最大值,即限制获取读锁的最大线程数 * 3.cas更新读锁同步状态是否成功 */ if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //可以获取到读锁 //r==0表示是第一个获取读锁的线程 if (r == 0) { firstReader = current; //记录第一个线程读锁的重入次数 firstReaderHoldCount = 1; } else if (firstReader == current) { //是第一个获取读锁线程,锁重入,锁重入次数+1 firstReaderHoldCount++; } else { // 已有其他线程获取到读锁 /* *1,获取缓存记录的计数器,计数器是用来统计每一个获取读锁线程的重入次数的, *由每个线程的ThreadLocal,即线程内的副本存储,相互独立; *此处也不是放入缓存,在有多个线程同时获取读锁情况, *用一个变量记录上一个获取读锁的线程的计数器,可能考虑多次获取读锁线程大概率是同一个线程情况, *这样做是可提高执行效率 */ HoldCounter rh = cachedHoldCounter; // rh==null,第一个获取读锁,rh没有值 // 或者计数器存储的上一次线程的id与当前线程不等, 即不是相同一个线程, //那么就获取当前线程内部的计数器,并赋值给cachedHoldCounter变量,这样可以让下一次获取读锁线程获取比较了 if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) /*进入该条件,我理解是在线程获取读锁再释放后,同一线程再次获取读锁情况, * 缓存计数器会记录上一个线程计数器,因为线程释放读锁后,count=0, * 这里重新将计数器放入线程内部中, * 因为线程在使用完线程内部变量后会防止内存泄漏,会执行remove,释放本地存储的计数器。 */ readHolds.set(rh); //计数器+1 rh.count++; } return 1; } //上面3个条件没有同时满足,没有成功获取到读锁,开始无限循环尝试去获取读锁 return fullTryAcquireShared(current); }无限循环尝试获取共享锁 fullTryAcquireShared方法
主要执行步骤:
1、 如果有其他线程获取到了写锁,写读互斥,应该去挂起等待;