JAVA's explicit lock

Lock interface

//获取不到就阻塞,不响应中断
void lock();
//获取不到就阻塞,响应中断
void lockInterruptibly() throws InterruptedException;
//获取到锁立即返回true,否则返回false
boolean tryLock();
//获取不到锁就阻塞,直到获取到锁,返回true;或者时间到,返回false;或者被中断,抛出异常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//解锁
void unlock();
//返回一个Condition实例,该实例与lock实例绑定
Condition newCondition();

Condition interface

condition is condition

//执行此方法后,线程将释放与之相关的锁并阻塞,直到其他线程执行同一个condition的signal或者signall方法
//或者被其他线程中断
//退出此方法,线程将重新获取相关锁
void await() throws InterruptedException;
//剩下这些await方法应该很好猜了,那么我就只标注返回值
//等到了时间终止才返回,则返回false,否则true
boolean await(long time, TimeUnit unit) throws InterruptedException;
//返回剩余纳秒数(估计值)
long awaitNanos(long nanosTimeout) throws InterruptedException;
void awaitUninterruptibly();
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒一个等待该condtion的类
void signal();
//唤醒所有等待该condtion的类
void signalAll();

ReentrantLock

Reentrant locks implement the Lock interface and serializable interface. The
related functions are completed through the internal class Sync. This class is an abstract class that inherits the abstract class AbstractQueuedSynchronizer.
This class has two subclasses, which are also internal classes of ReentrantLock, which are FairSync. , NonfairSync, represents fair locks and unfair locks. The default non-fair locks
are also complicated. Let's take a look at what happens when the lock method is executed by default.

Lock method execution process

public void lock() {
    sync.lock();
}

sync is a reference to the Sync class, which by default points to an instance of NonfairSync

//ReentrantLock的无参构造器
public ReentrantLock() {
    sync = new NonfairSync();
}

Of course, you can use a parameterized constructor to specify when it is fair

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Take a look at how the lock method of NonfairSync is implemented

final void lock() {
    //使用CAS改变线程状态,如果成功,修改锁的拥有者
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
    //否则,阻塞式获取
        acquire(1);
}

Focus on the acquire method

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

From left to right, first look at the tryAcquire method.
Note that EXCLUSIVE means exclusive, the actual value is null
after a series of calls, and finally, this method will call the nonfairTryAcquire method in Sync

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //获取当前锁的状态,c=0表示当前锁没有被占用,否则表示被占用了
    //CAS是乐观锁(但是并不意味着ReentrantLock就是一个乐观锁)
    //所以第一次失败后再执行一次相当正常
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
     //如果CAS再次失败,判断这个锁是不是已经被当前线程持有了(可重入)
    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;
}

Looking back at the acquire method, if tryAcquire fails, the acquireQueued method will be executed, where addwaiter adds the current thread to the waiting queue, which is implemented by a linked list, and the return value is the new node

final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //一开始以为这个死循环就是所谓的阻塞方式
        //但parkAndCheckInterrupt才是让它阻塞的方式
        for (;;) {
            //注意p是当前节点的前一个节点哦
            final AbstractQueuedSynchronizer.Node p = node.predecessor();
            //如果获取成功,就返回.这里是唯一退出循环的地方
            if (p == head && tryAcquire(arg)) {
                //获取成功,设置当前节点为head节点
                //可以看出,head节点是什么信息都没有的
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //判断node是否可阻塞,如果是,则调用parkAndCheckInterrupt
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

Through this series of calling processes, we don't seem to see where the so-called unfairness
is. The queue realized by the linked list is first-in
, first-out. In fact, this unfairness is only reflected in nonfairTryAcquire.The new thread can try to acquire the lock directly.
However, if you fail, you still have to line up honestly

Take another look at what shouldParkAfterFailedAcquire is doing

private static boolean shouldParkAfterFailedAcquire(AbstractQueuedSynchronizer.Node pred, AbstractQueuedSynchronizer.Node node) {
    int ws = pred.waitStatus;
    //这里的SIGNAL就是一个标记,表示下一个节点可阻塞
    if (ws == AbstractQueuedSynchronizer.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.
         */
        //否则,设置pred的状态为SIGNAL
        //这意味着,第二次调用这个方法一定会返回true(其实不然,要是有其他线程执行相关操作,那第二次可能还是false,详情去看解锁)
        compareAndSetWaitStatus(pred, ws, AbstractQueuedSynchronizer.Node.SIGNAL);
    }
    return false;
}

As can be seen from this code, if it is looped to the second time, shouldParkAfterFailedAcquire will return true, which means that parkAndCheckInterrupt will definitely execute
the method called by parkAndCheckInterrupt.There are many methods.To sum up, it finally calls the unsafe park method.This method is A local method should be to let the thread acquire the lock blocking

So far, we have roughly finished the locking process.To sum up,
for unfair locks, if a thread wants to lock, then this thread will first try to use CAS to obtain the lock.This process does not care about the threads waiting in the queue. unfair
If the acquisition fails, call acquire methods
in acquire, the first call tryacquire method calls nonfairTryAcquire method, this method here first attempt to use the CAS to acquire the lock, if that fails, then check whether the lock is to be the current thread have, This shows that
after the reentrant tryacquire fails, the current thread is added to the waiting queue, and the acquireQueued method is called to start waiting.The
waiting process is an infinite loop, and only exits after acquiring the
lock.The following steps are in the loop.AcquireQueued will first determine that the current Is the thread the first element of the queue? Note that the Head node does not have any information.The first element of the queue here actually refers to a node after the Head node.If it
is, then use CAS to lock
again.If it fails, then call shouldParkAfterFailedAcquire to determine Whether the current thread should be blocked, if it shouldn't, then A method will be the thread becomes blocked, then back into the false
That is, the second call shouldParkAfterFailedAcquire returns true (generally)
as long as shouldParkAfterFailedAcquire returns true, then the current thread will begin blocking, know that you can get a lock So far,
note that after this method exits, it will continue to loop, and the CAS will acquire the lock

This is the end of the summary,
but we will definitely notice a strange line of code.
In acquire, we call selfInterrupt () and interrupt ourselves.
What is this doing?
In fact, if the locking process is carried out normally and has not been interrupted, acquireQueued will Will return false, then this self-interruption will not be executed
at all.If it has been interrupted, then acquireQueued will not respond, and the interrupted method will be called to reset the relevant interrupt flag.
So this is to call self-interruption again and reply to the interrupted state.

unlock method

Unlock by release

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    //尝试解锁
    if (tryRelease(arg)) {
        AbstractQueuedSynchronizer.Node h = head;
        if (h != null && h.waitStatus != 0)
            //让后继节点不在处于可被阻塞装态,可能会影响其他线程shouldParkAfterFailedAcquire方法的返回值哦
            unparkSuccessor(h);
        return true;
    }
    return false;
}

Let us first look at the tryRelease method

protected final boolean tryRelease(int releases) {
    //state其实就是这个lock被执行了几次上锁操作
    //回顾一下可重入锁对于同一个线程上锁的原理
    //立马就可以知道,只有c为0,才能进行真正地解锁操作
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //真正的解锁
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    //只是让state的值减一
    setState(c);
    return free;
}

Next, take a look at what the unparkSuccessor method is doing.
This method will only be executed after the current thread discards the lock.

private void unparkSuccessor(AbstractQueuedSynchronizer.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.
     */
    AbstractQueuedSynchronizer.Node s = node.next;
    //寻找head节点后第一个被阻塞的节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

We noticed that the process of finding the first node is actually from the back to the front.
This is because if a new thread is added to the queue, it will be added to the end. In this case, the next of the node may occur during traversal The change makes the thread unsafe problem occur ( questionable , think first)
If you look back, the prev will not change, so there is no problem.When
a new thread joins the waiting queue, there is a detail that I have not limited in space. , That is, after putting the current thread into node, you must first set the node's prev, and then set node to tail

Fair lock

Without the previous queue insertion process, put it directly in the queue
and the unlock process is exactly the same as the unfair lock, then this is the way to go

The method of locking and unlocking is introduced here. Next is Condition

newCondition method

This method simply calls the ConditionObject constructor to obtain a new Condition instance.
This constructor does nothing, so in this case, we can only interpret this ConditionObject class in detail.

ConditionObject

This class turned out to be an internal class of AQS, so it seems understandable that the constructor did n’t do anything.
We know that Condition must be associated with a lock. If it is an internal class, it is inherently related.

Let ’s first look at how the await method is implemented

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //新增一个等待节点,这个等待节点就是当前线程,状态是condition
    //addConditionWaiter将新增一个节点,将其置于队列尾,并返回这个新建的节点
    Node node = addConditionWaiter();
    //释放锁,并获取线程状态值
    //状态值就是上了几次锁
    //如果这个方法解锁失败了,将会把node的状态置为CANCEL
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //判断node是否在同步队列上
    //如果没有其他线程执行signal方法
    //那这里应该在等待队列上
    while (!isOnSyncQueue(node)) {
        //阻塞
        LockSupport.park(this);
        //如果线程被中断过,则退出循环
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //重新获取锁成功且没有被中断
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    //去除后方状态为Cancel的Node
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

Next is the signal

private void doSignal(Node first) {
    do {
        //取出第一个node
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    //循环条件是将first置于同步队列失败并且
    //等待队列不为空
    //也就是说,唤醒第一个失败,那就该唤醒第二个了
    //但是失败的那个也没有任何保存措施,直接丢弃了?
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

Summary:
the await method of execution condition, the current thread will be placed on a waiting column.
This queue is that each has a condition, and that queue and locked when we saw earlier is not the same queue
in the analysis The synchronization queue mentioned in the await method is the queue waiting for the lock (this name is my nonsense) .In the
code, there is also a distinction between these two queues.Although they both use Node, in the synchronization queue, the front and back nodes It is prev and next, the wait queue uses nextWaiter, and does not record the previous node

After executing await, put the thread in the waiting queue first, and release the lock, waiting for other threads to wake up.After
executing the signal, the current thread will wake up the first thread on the waiting queue that can be woken up, and cannot be dropped directly.

So far we have not seen how the timing method is implemented, let us take a look at await (long time, TimeUnit unit)

public final boolean await(long time, TimeUnit unit)
        throws InterruptedException {
    //先转化为纳秒
    long nanosTimeout = unit.toNanos(time);
    if (Thread.interrupted())
        throw new InterruptedException();

    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);

    //设置截止时间
    final long deadline = System.nanoTime() + nanosTimeout;
    boolean timedout = false;
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        //超时了
        if (nanosTimeout <= 0L) {
            timedout = transferAfterCancelledWait(node);
            break;
        }
        if (nanosTimeout >= spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanosTimeout);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
        nanosTimeout = deadline - System.nanoTime();
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
    return !timedout;
}

This method itself has no focus,
but due to LockSupport.parkNanos

public static void parkNanos(Object blocker, long nanos) {
    if (nanos > 0) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, nanos);
        setBlocker(t, null);
    }
}

The local method park itself can have time parameters,
so it can be timed

AQS(AbstractQueuedSynchronizer)

In fact, a large part of the code introduced above is the code in AQS,
but it is not distinguished in
this article.Basically, this abstract class is a queue of threads, so most of the code related to the queue above appears in this class
. This class is expensive as an abstract class, but I did not find an abstract method

Guess you like

Origin www.cnblogs.com/ZGQblogs/p/12709520.html
Recommended