ReentrantLock 有两种实现锁的方式 一种是非公平的,一种是公平的,默认的实现方式是非公平的,但不管哪种实现方式,ReentrantLock都是依靠的它的静态内部了syc来实现,底层的实现机制都是利用volatitle语义 和操作系统提供的CAS原语来实现
所以在看本篇博文时请确保你有以上基础 ,下面上源码 (jdk1.8)
public ReentrantLock() { sync = new NonfairSync(); //默认的实现是非公平锁 } 静态内部类的实现
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { //这里获取锁 if (compareAndSetState(0, 1)) //这里是获取锁的关键 setExclusiveOwnerThread(Thread.currentThread()); //将当前拥有锁的线程置为当前线程,方便重入 else //获取失败则加入等待队列 acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
//获取锁的方法
protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); //调用CAS设置锁的状态,如state为0置为1返会true ,否则返回false 获取锁失败 }
还记得前面在lock时 如果失败则会加入队列 ,那当前线程是怎样加入队列的呢? 其实 每个线程都会被封装成一个node节点,每个node节点都有一个前指针prev和后指针next 然后将一个一个node节点连接起来,
接下来我们分析竞争锁失败的情况 失败将会调用
acquire(1);函数
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
我们先看 tryAcquire(arg)
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); //获取当前线程 int c = getState(); //获取当前状态 0为可用,1为被锁住 if (c == 0) { if (!hasQueuedPredecessors() && 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; }
tryAcquire 有两个作用,第一个作用是 再一次竞争锁,虽然线程在第一次竞争失败但有可能持有锁的线程持有时间非常短,
所以重新竞争
第二个作用是方便锁的冲入,一个线程可以重入该锁 并将计数加一,否则返回false
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
上面的判断是个&&判断,当第一个为!tryAcquire(arg) 为假时直接返回,说明当前竞争的线程是冲入或者持有锁的线程释放后被线程获取,假设,线程没有获取到 则会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))首先让我们先看下addWaiter(Node.EXCLUSIVE)方法 其中Node.EXCLUSIVE是个常量值 为空
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode);//创建一个节点,并将当前线程赋予该节点 // Try the fast path of enq; backup to full enq on failure Node pred = tail; //取出尾节点 if (pred != null) { //接下来的操作是将当前节点加入到链表的为节点 node.prev = pred; if (compareAndSetTail(pred, node)) { //可能这里你会有疑问如果失败怎么办?岂不是会丢失 别急 往下看 pred.next = node; return node; } } enq(node); //注意这个函数,关键就在这里 return node; }
private Node enq(final Node node) { // for (;;) { //相当于while循环,知道下面设置成功才退出 Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
看到上面的函数没 ,该函数会无限重试,直至成功, 到这里最终会被加入到链表末尾,之后会执行 acquireQueued()函数是自旋锁的获取过程,具体的可以看另一位老铁的博客,acquireQueued的作用我也是参考它的博看才明白的下面是链接