此文已由作者赵计刚授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
3.3.3、AbstractQueuedSynchronizer:acquireQueued(final Node node, int arg)
final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; /* * 无限循环(一直阻塞),直到node的前驱节点p之前的所有节点都执行完毕,p成为了head且node请求成功了 */ for (;;) { final Node p = node.predecessor();//获取插入节点的前一个节点p /* * 注意: * 1、这个是跳出循环的唯一条件,除非抛异常 * 2、如果p == head && tryAcquire(arg)第一次循环就成功了,interrupted为false,不需要中断自己 * 如果p == head && tryAcquire(arg)第一次以后的循环中如果执行了挂起操作后才成功了,interrupted为true,就要中断自己了 */ if (p == head && tryAcquire(arg)) { setHead(node);//当前节点设置为头节点 p.next = null; return interrupted;//跳出循环 } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true;//被中断了 } } catch (RuntimeException ex) { cancelAcquire(node); throw ex; } }
AbstractQueuedSynchronizer:shouldParkAfterFailedAcquire(Node pred, Node node)
/** * 检测当前节点是否可以被安全的挂起(阻塞) * @param pred 当前节点的前驱节点 * @param node 当前节点 */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;//获取前驱节点(即当前线程的前一个节点)的等待状态 if (ws == Node.SIGNAL)//如果前驱节点的等待状态是SIGNAL,表示当前节点将来可以被唤醒,那么当前节点就可以安全的挂起了 return true; /* * 1)当ws>0(即CANCELLED==1),前驱节点的线程被取消了,我们会将该节点之前的连续几个被取消的前驱节点从队列中剔除,返回false(即不能挂起) * 2)如果ws<=0&&!=SIGNAL,将当前节点的前驱节点的等待状态设为SIGNAL */ if (ws > 0) { do { /* * node.prev = pred = pred.prev; * 上边这句代码相当于下边这两句 * pred = pred.prev; * node.prev = pred; */ node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * 尝试将当前节点的前驱节点的等待状态设为SIGNAL * 1/这为什么用CAS,现在已经入队成功了,前驱节点就是pred,除了node外应该没有别的线程在操作这个节点了,那为什么还要用CAS?而不直接赋值呢? * (解释:因为pred可以自己将自己的状态改为cancel,也就是pred的状态可能同时会有两条线程(pred和node)去操作) * 2/既然前驱节点已经设为SIGNAL了,为什么最后还要返回false * (因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了) */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
AbstractQueuedSynchronizer:
private final boolean parkAndCheckInterrupt() { LockSupport.park(this);//挂起当前的线程 return Thread.interrupted();//如果当前线程已经被中断了,返回true }
以上就是一个线程获取非公平锁的整个过程(lock())。
4、公平锁的lock()
具体用法与非公平锁一样
如果掌握了非公平锁的流程,那么掌握公平锁的流程会非常简单,只有两点不同(最后会讲)。
简化版的步骤:(公平锁的核心)
获取一次锁数量,
B1、如果锁数量为0,如果当前线程是等待队列中的头节点,基于CAS尝试将state(锁数量)从0设置为1一次,如果设置成功,设置当前线程为独占锁的线程;
B2、如果锁数量不为0或者当前线程不是等待队列中的头节点或者上边的尝试又失败了,查看当前线程是不是已经是独占锁的线程了,如果是,则将当前的锁数量+1;如果不是,则将该线程封装在一个Node内,并加入到等待队列中去。等待被其前一个线程节点唤醒。
源代码:
4.1、ReentrantLock:lock()
4.2、FairSync:lock()
final void lock() { acquire(1); }
4.3、AbstractQueuedSynchronizer:acquire(int arg)就是非公平锁使用的那个方法
4.3.1、FairSync:tryAcquire(int acquires)
/** * 获取公平锁的方法 * 1)获取锁数量c * 1.1)如果c==0,如果当前线程是等待队列中的头节点,使用CAS将state(锁数量)从0设置为1,如果设置成功,当前线程独占锁-->请求成功 * 1.2)如果c!=0,判断当前的线程是不是就是当下独占锁的线程,如果是,就将当前的锁数量状态值+1(这也就是可重入锁的名称的来源)-->请求成功 * 最后,请求失败后,将当前线程链入队尾并挂起,之后等待被唤醒。 */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (isFirst(current) && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
最后,如果请求失败后,将当前线程链入队尾并挂起,之后等待被唤醒,下边的代码与非公平锁一样。
总结:公平锁与非公平锁对比
最后说一句,
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。