Java multi-threaded (9)

Java multi-threaded (9)

AQS(2)

Possession and release the lock

For AQS, a critical thread synchronization state is the state value of the operation, is divided into shared and exclusive manner according to whether a thread state, the operation state mode

  • Under an exclusive way to obtain the release of resources and methods used are:
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


     public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

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

  • Pan-sharing under way to acquire and release resources for the method:
public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    } 

Use exclusively available resources with specific binding thread is a thread gets the resources, it will mark this thread is to get the other threads try will find current operating state of the resource is not getting the resources they hold , will be blocked after a failed acquisition

Threads share resources with specific approach is not relevant when multiple threads request access to resources by resource competition CAS mode, when a thread gets to the resource, another one thread to acquire again if the current resources can meet its needs , the current thread only need to use CAS ways to get

Exclusive acquire and release resources processes:

  • (1)

当一个线程调用 acquire(int arg)方法获取独占资源时,会首先使用tryAcquire方法尝试获取资源,具体就是设置状态变量state的值,成功就直接返回,失败就将当前线程封装为类型Node.EXCLUSIVE的Node节点后插入到AQS阻塞队列的尾部,并调用LockSupport.park(this)方法挂起自己


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


  • (2)

当一个线程调用release方法时会尝试使用tryRelease操作释放资源,这里是设置状态变量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread),被激活的线程使用tryAcquire尝试,看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下进行,否则还是会被放入AQS队列并被挂起

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


detail:

AQS-based and do not provide tryAcquire tryRelease methods available to achieve this requires a specific sub-class, subclass used in implementing these methods for the scene CAS algorithm attempts to modify the state status value, returns true if successful, otherwise returns false, subclasses need to define when calling acquire and release methods increase or decrease state What is the meaning of the status values

Sharing resources to acquire and release process:

  • (1)

线程调用acquireShared获取共享资源时,首先使用tryAcquireShared尝试获取资源,具体设置状态变量state的值,成功则直接返回,失败则将当前线程封装为Node.SHARED的Node节点后插入到AQS阻塞队列的尾部,并使用LockSupport.park(this)方法挂起自己


public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }


  • (2)

当一个线程调用releaseShared时会尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)激活AQS队列里面被阻塞的一个线程(thread),被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    } 

detail:

AQS class does not provide a useful and tryReleaseShared tryAcquireShared methods, the two methods need to implement a sub-subclass, attempting to modify the use state of the status value CAS algorithm for the scene when the two methods to achieve this, returns true if successful, otherwise false

Based lock AQS implemented in addition to the above method needs to be rewritten, but also to rewrite isHeldExclusively method to determine whether the current thread lock is exclusive or shared

The meaning of which method without Interruptibly keywords are not interrupt response, that is, access to resources in the thread method calls without Interruptibly keywords or access to resources when failure is suspended, other threads interrupting the thread, the thread will not be interrupted and throws an exception, it continued access to resources or is suspended, that is not to interrupt response, ignoring interrupts

Exclusive lock mode

Lock acquisition process:

  • When the thread calls acquire () application to acquire the lock resources, if successful, enter the critical region.

  • When acquiring the lock fails, then enter a FIFO queue, then suspended to wait wake.

  • When the thread queue waiting to be awakened again attempt to acquire the lock on the resource, if successful, enter the critical section, or continue to wait to hang

Lock release process:

  • When a thread calls release () lock resources be released if no other thread is waiting for the resource lock, then release complete

  • If the queue there are other threads wait for a lock resources need to wake up, the first node in the queue waiting for the wake-up (first in first out)

acquire(int arg)

Acquire a lock:

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

logic:

  • Call subclass implementation of tryAcquire () method attempts to acquire a lock resources, if successful, the entire acquire () method is finished, that the current thread to acquire a lock resource, you can enter the critical section

  • If the lock acquisition failed, the logic begins to enter behind, first addWaiter (Node.EXCLUSIVE) Method

// 该入队方法的返回值就是新创建的节点
private Node addWaiter(Node mode) {
        //基于当前线程,节点类型(Node.EXCLUSIVE)创建新的节点
        //由于这里是独占模式,因此节点类型就是Node.EXCLUSIVE
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //这里为了提搞性能,首先执行一次快速入队操作,即直接尝试将新节点加入队尾
        if (pred != null) {
            node.prev = pred;
            //这里根据CAS的逻辑,即使并发操作也只能有一个线程成功并返回,其余的都要执行后面的入队操作。即enq()方法
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

Enqueue operation:

//完整的入队操作
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果队列还没有初始化,则进行初始化,即创建一个空的头节点
            if (t == null) { 
                //同样是CAS,只有一个线程可以初始化头结点成功,其余的都要重复执行循环体
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //新创建的节点指向队列尾节点,毫无疑问并发情况下这里会有多个新创建的节点指向队列尾节点
                node.prev = t;
                //基于这一步的CAS,不管前一步有多少新节点都指向了尾节点,这一步只有一个能真正入队成功,其他的都必须重新执行循环体
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    //该循环体唯一退出的操作,就是入队成功(否则就要无限重试)
                    return t;
                }
            }
        }
    }


  • Triggers initialization of the queue is the current thread owns the lock has resources, empty head node created above the current node is considered possession of a lock resource (although it does not set any property)

  • The entire code is in a cycle of death, knowing that the team be successful. If it fails, it will continue to retry

After the above operation, the thread acquires the lock of the application has successfully joined the queue, the next node to do now is to suspend the current thread, waiting to be awakened



// 该方法入参node就是刚入队的包含当前线程信息的节点
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; //帮助GC
                    //表示锁资源成功获取,因此把failed置为false
                    failed = false;
                    //返回中断标记,表示当前节点是被正常唤醒还是被中断唤醒
                    return interrupted;
                }
                如果没有获取锁成功,则进入挂起逻辑
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //获取锁失败处理逻辑
            if (failed)
                cancelAcquire(node);
        }
    }

Suspend logic so far, but according to the current thread, the type of node creates a node and join the queue, other attributes are default values

//node是当前线程的节点,pred是它的前置节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前置节点的waitStatus
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //如果前置节点的waitStatus是Node.SIGNAL则返回true,然后会执行parkAndCheckInterrupt()方法进行挂起
            return true;
        if (ws > 0) {
            //由waitStatus的几个取值可以判断这里表示前置节点被取消
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //这里我们由当前节点的前置节点开始,一直向前找最近的一个没有被取消的节点
            //注,由于头结点head是通过new Node()创建,它的waitStatus为0,因此这里不会出现空指针问题,也就是说最多就是找到头节点上面的循环就退出了
            pred.next = node;
        } else {
            //根据waitStatus的取值限定,这里waitStatus的值只能是0或者PROPAGATE,那么我们把前置节点的waitStatus设为Node.SIGNAL然后重新进入该方法进行判断
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

The above method of logic to determine whether the current node may be suspended, i.e. whether the wake-up condition has been met, i.e., if hung, it must be possible to wake up the other threads. If this method returns false, that is pending condition is not complete, it will re-execute the loop body acquireQueued methods, re-evaluated and if returns true, it means everything is ready, you can hang up, will enter parkAndCheckInterrupt () method

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        //被唤醒之后,返回中断标记,即如果是正常唤醒则返回false,如果是由于中断醒来,就返回true
        return Thread.interrupted();
    }

The final step acquireQueued method, finally module. Here is a failure for later retrieval lock resources to do some remedial work


//传入的方法参数是当前获取锁资源失败的节点
private void cancelAcquire(Node node) {
        // 如果节点不存在则直接忽略
        if (node == null)
            return;
        
        node.thread = null;

        // 跳过所有已经取消的前置节点,跟上面的那段跳转逻辑类似
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;
        //这个是前置节点的后继节点,由于上面可能的跳节点的操作,所以这里可不一定就是当前节点,仔细想一下。^_^
        Node predNext = pred.next;

        //把当前节点waitStatus置为取消,这样别的节点在处理时就会跳过该节点
        node.waitStatus = Node.CANCELLED;
        //如果当前是尾节点,则直接删除,即出队
        //注:这里不用关心CAS失败,因为即使并发导致失败,该节点也已经被成功删除
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    //这里的判断逻辑很绕,具体就是如果当前节点的前置节点不是头节点且它后面的节点等待它唤醒(waitStatus小于0),
                    //再加上如果当前节点的后继节点没有被取消就把前置节点跟后置节点进行连接,相当于删除了当前节点
                    compareAndSetNext(pred, predNext, next);
            } else {
                //进入这里,要么当前节点的前置节点是头结点,要么前置节点的waitStatus是PROPAGATE,直接唤醒当前节点的后继节点
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

Release the lock

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

tryRelease () method releases the lock logic is implemented in a subclass, if successful, it is determined that there is no node in the queue waiting to be awakened (waitStatus 0 indicates no node needs to be awake)

Wake-up operation

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            //把标记为设置为0,表示唤醒操作已经开始进行,提高并发环境下性能
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        //如果当前节点的后继节点为null,或者已经被取消
        if (s == null || s.waitStatus > 0) {
            s = null;
            //注意这个循环没有break,也就是说它是从后往前找,一直找到离当前节点最近的一个等待唤醒的节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //执行唤醒操作
        if (s != null)
            LockSupport.unpark(s.thread);
    }



Shared lock mode

Lock acquisition process:

  • When a thread calls acquireShared () application to acquire the lock resources, if successful, enter the critical region.

  • When acquiring the lock fails, create a shared type of node and into a FIFO queue, then wait wake is suspended

  • When the thread queue waiting to be awakened again attempt to acquire the lock on the resource, if successful wake behind a shared node and is still waiting for the wake-up event to pass along, that will in turn awaken all shared nodes in the back of the node, then enter critical section, otherwise continue to hang wait.

Lock release process:

  • When a thread calls releaseShared () lock resources be released, if the release is successful, the node waiting in the queue is awakened, if any.

Acquire a lock:

  public final void acquireShared(int arg) {
        //尝试获取共享锁,返回值小于0表示获取失败
        if (tryAcquireShared(arg) < 0)
            //执行获取锁失败以后的方法
            doAcquireShared(arg);
    }

tryAcquireShared () method is to acquire a lock logic specific left sub-class to implement

On the realization of this approach are two points that need special instructions

  • The method must check their own whether to support the current context acquire a shared lock, if supported by further acquisition.

  • This method returns a value is key. First, the above code fragment can be seen that the return value is less than 0 indicates a failure to acquire the lock, need to enter the queue. Second, if the return value is equal to 0 indicates that the current thread acquires a shared lock success, but it can not continue to follow the thread is obtained, that is, do not need to wake node waiting behind it. Finally, if the return value is greater than 0 indicates that the current thread acquires a shared lock and its subsequent success waiting node is also likely to continue to acquire a shared lock is successful, that is to say at this time need to follow them to wake node attempts to acquire a shared lock.

doAcquireShared()


  //参数不多说,就是传给acquireShared()的参数
    private void doAcquireShared(int arg) {
        //添加等待节点的方法跟独占锁一样,唯一区别就是节点类型变为了共享型,不再赘述
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //表示前面的节点已经获取到锁,自己会尝试获取锁
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    //注意上面说的, 等于0表示不用唤醒后继节点,大于0需要
                    if (r >= 0) {
                        //这里是重点,获取到锁以后的唤醒操作,后面详细说
                        setHeadAndPropagate(node, r);
                        p.next = null;
                        //如果是因为中断醒来则设置中断标记位
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //挂起逻辑跟独占锁一样,不再赘述
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //获取失败的取消逻辑跟独占锁一样,不再赘述
            if (failed)
                cancelAcquire(node);
        }
    }

Set the first node and then returns the interrupt status after successfully acquiring an exclusive lock mode, the end of the process, and later shared lock mode for success, calling the method setHeadAndPropagate


 //两个入参,一个是当前成功获取共享锁的节点,一个就是tryAcquireShared方法的返回值,注意上面说的,它可能大于0也可能等于0
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; //记录当前头节点
        //设置新的头节点,即把当前获取到锁的节点设置为头节点
        //注:这里是获取到锁之后的操作,不需要并发控制
        setHead(node);
        //这里意思有两种情况是需要执行唤醒操作
        //1.propagate > 0 表示调用方指明了后继节点需要被唤醒
        //2.头节点后面的节点需要被唤醒(waitStatus<0),不论是老的头结点还是新的头结点
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //如果当前节点的后继节点是共享类型获取没有后继节点,则进行唤醒
            //这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒
            if (s == null || s.isShared())
                //后面详细说
                doReleaseShared();
        }
    }

    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }


Wake-up operation:


private void doReleaseShared() {
        for (;;) {
            //唤醒操作由头结点开始,注意这里的头节点已经是上面新设置的头结点了
            //其实就是唤醒上面新获取到共享锁的节点的后继节点
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //表示后继节点需要被唤醒
                if (ws == Node.SIGNAL) {
                    //这里需要控制并发,因为入口有setHeadAndPropagate跟release两个,避免两次unpark
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;      
                    //执行唤醒操作      
                    unparkSuccessor(h);
                }
                //如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保以后可以传递下去
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                
            }
            //如果头结点没有发生变化,表示设置完成,退出循环
            //如果头结点发生变化,比如说其他线程获取到了锁,为了使自己的唤醒动作可以传递,必须进行重试
            if (h == head)                   
                break;
        }
    }

Shared lock is released


public final boolean releaseShared(int arg) {
        //尝试释放共享锁
        if (tryReleaseShared(arg)) {
            //唤醒过程,详情见上面分析
            doReleaseShared();
            return true;
        }
        return false;
    }

Compared exclusive lock, the main feature is that when a shared lock on the shared node after waiting in the queue successfully acquired the lock (it gets to a shared lock), since it is shared, then it must be followed by all wake behind it is saying together with the current node shared resource lock.

Undoubtedly, these nodes must also be waiting for a shared lock (this is the premise that if an exclusive lock wait, that already have a shared node to acquire the lock, and it is certainly less than the acquisition). When a shared lock is released, it can be an example to think in read-write locks, when a read lock is released, then either a read lock or write lock can all be competing for resources

发布了191 篇原创文章 · 获赞 58 · 访问量 18万+

Guess you like

Origin blog.csdn.net/Coder_py/article/details/104071636