ReentrantLock ---- Lock、UnLock详解

1.非公平锁

ReentrantLock lock =  new ReentrantLock();

lock.lock();


非公平锁是由静态内部类实现,static final class NonfairSync extends Sync

final void lock() {
    //首先进行cas操作,如果获取到锁,则把该线程设置成独占锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
           acquire(1);
    }

假设有三个线程A、B、C,线程A先进入并将state设置为1,表示独占线程,即锁被A线程占用,此时线程B、C将进入else方法


public final void acquire(int arg) {
    //获取锁失败,并且成功加入到队列中时,则将该线程设置成中断模式
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


tryAcquire(arg)实现方法:

final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
    //c为0,表示可以抢夺该资源
        if (c == 0) {
           if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
     //记录是否是重入锁,如果是,则将state值加,不能溢出
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow//防止超过int的最大值,造成溢出。说明重入次数是有限制的
                throw new Error("Maximum lock count exceeded");
            setState(nextc);//设置state的值,说明调用多少次lock()方法,就要调用多少次unlock()方法
            return true;
       }
        return false;
    }

当线程进入到nonfairTryAcquire方法时,此时c的值为1(state是被volatile修饰,当线程A修改了此值,对线程B、C是可见的),并且当前的线程不是独占线程。所以获取失败返回false。(前提是假设A线程没有释放锁)


private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //队列不为空时,将node节点加入队尾
        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) { //表示队列为null,先初始化队列,并设置头结点和尾节点,然后进行循环,如果cas失败将一直循环直至成功的将传入的node节点加入队列,并设置尾节点为node节点
              if (compareAndSetHead(new Node()))
                tail = head;
          } else {
              node.prev = t;
              if (compareAndSetTail(t, node)) {
                 t.next = node;
                 return t;
              }
          }
      }
   }

假设线程B、C同时走到enq方法时,首先队列为空,先new Node节点并设置该节点是head和tail,然后再一次循环依次加入队列中,如图所示


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);// 获取成功,将当前节点设置为head节点
                      p.next = null; // 原head节点出队,在某个时间点被GC回收// help GC
                       failed = false;//获取成功
                       return interrupted;/返回是否被中断过
                 }
            // 判断获取失败后是否可以挂起,若可以则挂起
                    if (shouldParkAfterFailedAcquire(p, node) &&
                      parkAndCheckInterrupt())
              // 线程若被中断,设置interrupted为true
                       interrupted = true;
                }
         } finally {
            if (failed)
                    cancelAcquire(node);
        }
    }
只有满足条件才会跳出循环,根据上图所示只有线程B的前驱节点是head,则只有线程B先出队,线程C才有机会获取到锁。如果线程B获得锁,则将上图的Head节点删除,将B节点设置为头结点。锁被解了怎样保证整个FIFO队列减少一个Node呢
假设B、C线程获取所失败,则只需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;
        // 前驱节点状态为CANCELLED
            if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */// 从队尾向前寻找第一个状态不为CANCELLED的节点
                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;
    }


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

shouldParkAfterFailedAcquire会先判断当前节点的前驱是否状态符合要求,若符合则返回true,然后调用parkAndCheckInterrupt,将自己挂起。如果不符合,再看前驱节点的状态是否>0(CANCELLED),若是那么向前遍历直到找到第一个符合要求的前驱,若不是则将前驱节点的状态设置为SIGNAL。SIGNAL的含义就是当获取的锁时,并出队后唤醒后面的节点线程
线程B和C都已经入队,并且都被挂起。当线程A释放锁的时候,就会去唤醒线程B去获取锁。

unlock()方法:

public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
// 如果不是当前线程占用锁,那么抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
流程大致为先尝试释放锁,若释放成功,那么查看头结点的状态是否为SIGNAL,如果是则唤醒头结点的下个节点关联的线程,如果释放失败,那么返回false表示解锁失败。这里我们也发现了,每次都只唤起头结点的下一个节点关联的线程。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值