ReentrantLock加锁(lock())、释放锁(unlock())的实现

目录

lock()过程总结

lock 与 lockInterruptibly比较区别:

tryLock()过程总结

unLock()过程总结

lock()源码分析

1、抢占锁源码TryAquire():分析

2、队列形成及阻塞源码分析

1、形成队列过程

2、形成队列阻塞的过程

tryLock()源码分析:

unLock()源码分析


lock()过程总结

通常用法如下:

 public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try {
           //doSomething()
        } finally {
            lock.unlock();
        }
    }

 lock()关键代码:

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


1)先去判断state

  1. tate=0:compareAndSetState(0,1)执行成功,state=state+1=1,return true 代表获锁所成功
  2. state>0:则判断是否是重入,是重入,则state=state+1 return true 代表重入成功
  3. 其他:retun false 代表获取锁失败。

2)获取锁失败后,通过addWaiter(Node.EXCLUSIVE), arg)形成队列:

  1. 队列初始是空,则将head=new Node(),tail=head
  2. 队列不是空:将新节点链接到队列尾部

3)每当链接到队列里一个节点,首先通过 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)尝试获取锁:

  1. 获得锁成功:则不需要阻塞,直接将获取到锁的线程锁在的节点设置为头结点(如果连续加入队列连续尝试获取锁,连续获取成功,则连续设置头,则都不需要阻塞)
  2. 获得锁失败:则将前一个节点的waitStatus设置为signal,当前节点所在的线程通过LockSupport.park()阻塞。之后所有加入到队列里的节点都要跟着阻塞

lock 与 lockInterruptibly比较区别:


lock 优先考虑获取锁,待获取锁成功后,才响应中断。
lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。

lockInterruptibly的

ReentrantLock加锁(lock())、释放锁(unlock())的实现_第1张图片

ReentrantLock加锁(lock())、释放锁(unlock())的实现_第2张图片

LOCK的:

ReentrantLock加锁(lock())、释放锁(unlock())的实现_第3张图片

 

tryLock()过程总结

tryLock()的过程=非公平锁的tryAquire()tryLock()获取到锁:直接返回true,获取不到:直接返回false.并不进行入队和阻塞及被唤醒的一系列过程(lock()没有返回值,获取到会继续执行代码,获取不到会被入队及阻塞,等待着被唤醒再次抢占锁,直至抢占锁成功,代码执行完毕后才算结束)。通常用法如下:

public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        boolean isLocked = lock.tryLock();
        if (isLocked) {
            try {
                //doSomething
            } finally {
                lock.unlock();
            }
        }
    }
 

unLock()过程总结

unLock()关键代码:

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


unLock()一次:state-1,直到state=0 tryRelease()返回true,则会唤醒阻塞队列里的头节点的下一个节点,被唤醒的节点又去尝试获取锁:

  1. 获得锁成功:变为新的头(队列里就少了一个旧头节点,如果挨个唤醒,挨个获得成功,则阻塞队列里的节点一直在减少(等于一直在砍头,也是阻塞队列消失的一个过程,最后会消失成一个头,等着下次阻塞的时候再次当做阻塞队列的新头)
  2. 获得锁失败:再次LockSupport.park()被阻塞,等着被再次唤醒

lock()源码分析


 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
 AQS全局变量:state  tail head exclusiveThread

1、抢占锁源码TryAquire():分析


 tryAquire:执行tryAquire尝试获取锁的的有以下几种情况:

  1. 刚来的线程
  2. 刚链接到队列先尝试获取锁(只有头结点的下一个节点才有资格获取,这时有可能是加入一个获取成功一个,则头部节点设置为先加入的这个,此时有可能一连串的节点都不需要阻塞,一旦有一个节点获取锁不成功,才会被阻塞,下面的节点因为肯定不是头结点,都没有资格获取锁,当然也必须跟着阻塞)
  3. 也有可能是阻塞队列里被唤醒的线程


获取锁的过程:首先判断aqs里的state:

  1.  state=0:  非公平锁直接去抢占锁,执行compareAndSetState(0,1)执行成功代表抢占锁成功,此时state+1=0+1=1,并且设置独占线程是当前线程。return true代表获取锁成功 公平锁:先去判断阻塞队列里有没有优先于当前线程的线程获取过锁,有的话则需要加入阻塞队列进行阻塞,没有再去抢占锁
  2. state!=0:即锁被占有着,则看aqs里的全局变量独占线程是否是当前尝试获取锁的线程,如果是,则代表锁重入state=state+1,return true 代表获取锁成功
  3. 其他情况返回false

非公平锁获取:

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;
}


公平锁获取:
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;
}

公平锁和非公平锁的区别:

比对源码:发现唯一区别就是:发现state=0时

  1. 非公平锁:直接抢占锁,
  2. 公平锁:先去判断阻塞队列里有没有优先于当前线程的线程获取过锁,有的话则需要阻塞当前线程
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());
}

情况分析:

tail=head:代表当前线程无须阻塞可直接获取锁,情况分为2种:

  1. tail=head=null :此时阻塞队列为空可直接参与抢占锁
  2. tail=head=最后一个节点:此时这个节点肯定已经抢占锁并释放锁了,因为head只可能俩种:初始为null,还有一个是head为获取过锁成功的线程,现在head又不为null并且state=0,代表这个节点已经获取过了锁并释放了锁

tail!=head:

  1. tail!=head && h.next=null :需要阻塞:代表正在初始化队列,证明已经有先于当前线程的线程获取锁了
  2. tail!=head && h.next!=null && s.thread != Thread.currentThread():需要阻塞:代表该去抢占锁的是队列里头结点的下一个节点(s.thread != Thread.currentThread()代表有优先权的不是当前线程自己)

公平锁和非公平锁的相同点:

不管公平锁还是非公平锁:对于阻塞队列里的节点获取锁都是先进先出,即先进队列的节点先获取锁,后进队列的节点后获取锁,因为lockSupport.unPark()唤醒时从头节点的下一个节点依次向后开始唤醒。

2、队列形成及阻塞源码分析

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

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

 

1、形成队列过程

addWaiter(Node.EXCLUSIVE), arg)


!tryAcquire(arg)=true代表获取锁失败,则连接到队列:

  1. aqs里的tail=null,代表队列还没有,就初始化队列,执行enq(Node):将head=new Node()此时head为空节点,head赋值为tail,此时head=tail=new Node()=空节点
  2. aqs里的tail!=null,则将当前线程锁封装成的节点链接到队列尾部

(enq之所以for死循环是一个典型无锁编程的例子,意思是一个节点不一定能设置头部或尾成功,有可能被其他线程节点抢夺,所以一直循环,直到最终链接到队里执行成功compareAndSetTail成功才算执行完毕)
 

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;
            }
        }
    }
}


2、形成队列阻塞的过程

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))


往队列里每添加一个节点就要尝试去获取一下锁:

     获取锁成功就不用阻塞了,这时有可能是加入一个获取成功一个,则头部节点设置为当前加入的这个节点,此时有可能一连串的节点都获取成功那么都不需要阻塞,一旦有一个节点获取锁不成功,才会被阻塞,后面再有的新节点因为前面有节点被阻塞,都没有资格获取锁,当然也必须跟着阻塞

(之所以是一个for循环的过程:就是每次被唤醒,都尝试获取锁,获得成功,设置新的头结点,砍掉旧的头结点,获得失败:需要再次LockSupport.park阻塞,如果不是for循环,实现不了这种逻辑)
 

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

执行setHead(node)的情况:

  1. 刚开始加入队列未经阻塞直接获取了锁
  2. 阻塞被唤醒后继续往下执行for循环,唤醒后获取了锁

所以整个来看队列里头结点就只有2种情况:1.刚初始化队列时:new Node() 2.已经执行过了lock()=true的节点

为什么下面都是头结点的下一个节点,而不是头结点:
1.加入队列后只有头结点的下一个节点可以获取锁
2.头节点的下一个节点为唤醒的目标
就是因为头结点有上面的俩种情况:1.刚初始化队列时:new Node() 2.已经执行过了获取锁成功的操作 这俩种都不需要获取锁


 一个节点阻塞之前把他的前一个节点waitStatus设置为Node.SIGNAL:
 

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;
    }


开始阻塞:

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//形成阻塞的位置,也是将来被唤醒后继续往下执行的位置
        return Thread.interrupted();
    }

tryLock()源码分析:

等于非公平锁的tryAquire() ,故不再赘述

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
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;
}

unLock()源码分析

unLock()一次,aqs里的全局变量state-1:

  1. state!=0,则返回false 
  2. state=0 返回true,就去执行唤醒阻塞队列里头节点的下一个节点:unparkSuccessor
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;
}

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);//s为头结点的下一个节点,唤醒头结点的下一个节点,让他继续循环去获取锁
}

 

阻塞队列是怎么变没的:

  1. 开始加入队列一个,获取锁成功一个,设置一个头,可能一连串的未经阻塞,直接加入队列后就获取锁成功了,等于把一个队列里的头挨个从前往后砍掉
  2. 加入队列并且被阻塞了,那只能通过unLock()后并且state=0时唤醒了,挨个从头的下一个节点开始唤醒,唤醒后成功获取锁,则砍掉一个头,设置一个新头,唤醒后获取锁失败,则再次lockSupport.park()被阻塞,下次再被唤醒的还是他

(所以队列是通过将获取了锁的线程锁在的节点依次设置为新的头结点,挨个把旧头结点砍掉的,比如头结点的下一个节点获取了锁,则新的头结点变为旧的头结点的下一个节点,旧的头结点就被砍去了)

 

你可能感兴趣的:(并发编程)