JAVA并发中的ReentrantLock锁的实现

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
    }
    
    static final class NonfairSync extends Sync{
        ...
    }
    
  
    static final class FairSync extends Sync {
        ...
    }
    
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    // 获取锁
    public void lock() {
        sync.lock();
    }
    
    ...
}

内部类NonfairSync 和FairSync 继承了Sync ,内部类sync继承了AQS。

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            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;
        }
    }

 abstract static class Sync extends AbstractQueuedSynchronizer {
          ...
            final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
      ...
 }

公平锁和非公平锁有俩处不同:

1.获取锁时,非公平锁先执行一次CAS操作成功则获取到锁。

2.tryAcquire方法的不同,当state为0时,非公平锁直接CAS操作获取锁,而公平锁先判断当前的节点是否是sync队列中的第二个节点(头节点可以看做是持有锁的线程),如果是然后才会执行CAS操作去获取锁。另一种情况,持,有锁的线程就是当前线程,说明是锁重入,公平锁和非公平锁的逻辑是一样的。对state加1操作。

   public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这个acquire方法的实现是在父类AQS中实现的,这儿用到了一个常见的实际模式--模板(让子类重写了变化的部分tryAcuire方法)。

tryAcquire方法,尝试获取锁。逻辑很简单,就是CAS操作去改变state属性(volatile修饰的)值从0变为1(不考虑重入问题,这里存在竞争),如果重入则在原基础上加1(不存在竞争)。返回true,获取到锁,false则获取锁失败。在公平锁中,多了一个判断是否是第二节点的逻辑,hasQueuedPredecessors()方法。

   public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

是第二个节点时,那么结果是 return true&&(fasle|fasle) ,结果为false,可以去抢锁。本质就是检测自己是不是head节点的后继节点,即处在阻塞队列第一位的节点 。

接下来是acquireQueued方法(节点为head节点的后节点则获取锁,失败则判断是否挂起)。

我们接下来分析addWaiter方法,将节点加入到等待队列中。

   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 (;;) {
            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;
                }
            }
        }
    }

将当前线程包装为node对象,第一步判断尾节点是否为空,不为空,执行入队操作。

将一个节点加入sync队列如果一切顺利的话一共分了三步:

1.将当前节点A的prev指向尾节点T。

2.CAS操作,将当前节点A设置为新的尾节点A。

3.CAS成功,将之前的尾节点T的next指向新的尾节点A。

假设有俩个线程都执行了addWaiter方法,同时执行到CAS这一步,compareAndSetTail(pred, node)。

那么必然,只能有一个线程执行成功,假设线程t1执行成功,那么线程t1能执行到if里面的逻辑,然后return线程t1对应的节点node1。另一个线程t2CAS操作失败,接下来执行enq方法入队。

enq方法,第一个if对应的是队列为空的情况,先初始化队列,head节点给一个空节点,然后将tail指向这个空节点。这时候head==tail。然后执行下一次循环,执行到else中的逻辑。也就是说初始化队列sync时,先设置head值和tail值,等下一循环才会将传入的节点放到tail后面,即成为第二个节点(这里可能不是第二个节点,存在并发问题,但是这个节点总是会加入到sync中的)。

else里的逻辑就是之前分析的入队三步骤,只不过,将这个放在自旋逻辑中,那么节点最终都会加入到sync中,只有node加入到sync中,这个方法才会返回这个节点。

因为存在并发的问题,所以我们只能说addWaiter方法返回值是一个在等待队列中的节点,不一定还是尾节点(sync是共享资源,有多个线程对其进行修改)。

接下来就是acquireQueued方法,字面意思就是获取和等待,进一步释义就是阻塞获取锁。返回值true代表发生了中断,我们到时候在lock方法中会自我中断一下。

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

failed属性为false时,说明节点已经获取到锁,true时说明这个方法的try包裹的代码块发生了异常需要执行 cancelAcquire(node)方法。

在自旋逻辑中,第一个if条件满足时,说明是队列中的第二个节点,然后去尝试获取锁,如果获取成功将持有锁的节点设为头节点,将之前的节点next指向null,用于GC回收,然后return退出。

其他情况则需要判断是否要挂起,获取锁失败或者节点不是第二个节点。我们判断是否要挂起这个节点方法是shouldParkAfterFailedAcquire。

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

一个节点是否可以挂起,我们需要将它的前节点(有效的,未取消的)的waitstate设置为-1(Node.SIGNAL),然后再挂起当前节点对应的线程。第二个if(ws>0),我们需要将当前的节点一直向前找,直到找到一个节点是正常节点,然后prev指向那个正常节点。大白话就是抛弃它前面已经取消了的节点,直到遇到正常节点,然后挂在正常节点后面。最后,其他情况的状态值,直接CAS操作设置为-1状态。只有状态为-1时,直接返回true,代表可以挂起。其他状态我们只是修改为-1(对应0,-3时的情况)或者找到正常的前节点(对应状态为-2的情况),返回false。返回false后,继续上一步的自旋操作。

回到这个方法的调用处,返回的如果是ture,接下来执行parkAndCheckInterrupt(),将当前线程挂起。直到被唤醒或者中断,我们才会继续执行后续的操作,返回true代表线程中断导致的唤醒,false说明时正常的被唤醒。然后我们回到上一步,线程不管是何种方式被唤醒,都会自旋去获取锁。只有获取到锁,才会return,回到这个方法的调用处。获取锁后,判断有没有发生中断,如果有就执行自我中断。也就是说,我们在阻塞获取锁时对中断的响应不是实时的,是等线程拿到锁之后,做的时候延后处理。

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

题外话:为何我们有了Synchronized 还要lock呢?

个人觉得因为Synchronized有死锁问题,性能的话1.6之后Synchronized被优化过。

死锁发生有四个必要条件,我们只要能够破坏掉 “不可抢占”这个条件,那么就解决了Synchronized的死锁的问题。

因为线程使用Synchronized申请资源的时候,如果失败则直接进入阻塞,这时候也不会释放已有的资源,容易形成俩个线程相互等待,导致死锁。

我们如果能够在获取锁失败的时候,不进行阻塞;或者阻塞后还是可以响应中断然后释放资源;或者一定时间获取不到锁就放弃;那么就可以破环掉 “不可抢占”这个条件。

具体方法就是:

public boolean tryLock()          { return sync.tryAcquire(1); }

public void lockInterruptibly() throws InterruptedException {
   sync.acquireInterruptibly(1);
  }
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
      return sync.tryAcquireNanos(1, unit.toNanos(timeout));
 }

此外,我们在sync队列中的node,我们是可以随时取消的,状态改为CANCELLED即可。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值