Art Java concurrent programming (11) AQS queue synchronizer

Lock

Lock is used to control a plurality of threads shared resource access mode, in general, a lock can be prevented from simultaneously accessing a shared resource by multiple threads (some locks may allow multiple concurrent threads to access shared resources, such as read-write lock). Prior to Lock appears, Java program is a lock on the synchronized keyword function. After the Java SE 5, and the new contract in the Lock interface is used to achieve the locking function, which provides synchronization with the synchronized keyword similar function, but when you use need to explicitly acquire and release the lock. Although it lacks the implicit obtain the release of the lock (through synchronized blocks or methods provided) convenience, but it has a variety of synchronized lock acquisition and release key operability, and interruptible lock acquisition timeouts acquire lock do not have the word synchronization characteristics.
Using the synchronized keyword will implicitly acquire a lock, but it will lock acquisition and release of cure, and that is to get re-released. Of course, this approach simplifies the synchronization of management, but does not show the extensible lock acquire and release to the good. For example, for a scene, hands for lock acquisition and release, to get the lock A, and then acquire the lock B, when the lock B is obtained, release the lock A simultaneously acquire lock C, when the lock C is obtained, then release the B simultaneously acquire lock D, and so on. In this scenario, synchronized keyword is not so easy to implement, while using Lock it much easier.

    Lock lock = new ReentrantLock(); 
    lock.lock(); 
    try{                   不要将锁释放写在try代码块中,如果获取锁发生异常,会导致锁无故释放
    } finally{
        lock.unlock();     保证获取到锁之后,最终能够释放锁。
    }

The main characteristics of the synchronized keyword Lock interface provided does not have

characteristic description
Non-blocking attempt to acquire the lock The current thread tries to acquire the lock, if this time the lock is not acquired by another thread that holds the lock is successfully acquired and
Can interrupt access to the lock Synchronized with different thread can acquire the lock in response to interrupts, when the acquired lock the thread is interrupted, interrupt exception will be thrown, while the lock is released
Timeout acquire locks Acquire a lock before the specified deadline, if the deadline is still unable to acquire a lock, then return

Lock is an interface that defines the basic operation of the lock acquisition and release of

Method name description
void lock() To obtain a lock, the method is called the current thread will acquire the lock after acquiring the lock, returned from the method
void lockInterruptibly()throws InterruptedException Interruption may acquire the lock, and lock () method except that the method will respond to the interrupt, which can interrupt the current thread acquired the lock
boolean tryLock() Non-blocking attempt to acquire the lock, return immediately after the method is called, if we can get the returns true, false otherwise
boolean tryLock(long time,TimeUnit uuit)throws InterruptedException Acquiring the lock timeout, when the thread about the three cases will be returned: (1) the current thread to acquire the lock within the timeout period (2) the current thread is interrupted (3) the end of the timeout within the timeout period, returns false
void unlock() Release the lock
Condition newCondition() Gets waiting for a notification component that locks the binding and the current, the current thread only get a lock before calling wait () method of the component, and then call the current thread releases the lock

AQS queue synchronizer

Used to construct the base frame synchronization locks or other components, it uses a member variable int represents the synchronization state, access to resources to complete the work by the thread queue FIFO queue.
When the method specified rewrite synchronizer, the synchronizer requires the use of the following three methods provide access or modify the synchronization state
(1) getState (): Get the current synchronous state
(2) setState (int newState) : Set the current synchronous state
(3) compareAndSetState (int expect, int update) : CAS is used to set the current state of the process state can guarantee atomicity.
Method synchronizer rewritable

Method name description
protected boolean tryAcquire(int arg) Exclusive acquire synchronization state, to implement this method need to query the current status and determines whether the synchronization status in line with expectations, and then the CAS set up synchronization status
protected boolean tryRelease(int arg) Exclusive release synchronous state, waiting to acquire synchronization status of the thread will have the opportunity to acquire synchronization status
protected int tryAcquireShared(int arg) Shared acquire synchronization state, returns a value greater than or equal to 0, represents a successful acquisition, conversely, acquisition failure
protected boolean tryReleaseShared(int arg) Shared released synchronization status
protected boolean isHeldExclusively() Whether the current thread holds a synchronizer in exclusive mode, the process generally indicates whether the current thread monopolized

Template synchronization method provided by

Method name description
void acquire(int arg) Exclusive acquire synchronization status, if the current thread synchronization status successfully acquired, by the method returns, otherwise, it will queue waiting to enter the synchronized, the method will be called overridden tryAcquire (int arg) method
void acquireInterruptibly(int arg) And acquire (int arg) the same, but the method in response to the interrupt, the current thread to acquire the synchronization state to a synchronous queue, if the current thread is interrupted, this method throws an exception and interrupt return
boolean tryAcquireNanos(int arg,long nanos) Based on an increase in acquireInterruptibly timeout limit, if the current thread does not get to the synchronization status within the timeout period, then return false
void acquireShared(int arg) Shared access to the synchronized state, if the current thread does not acquire synchronization status, will enter the synchronization queue waiting, and when the exclusive acquisition of the main differences at the same time can have multiple threads to obtain the synchronization status
void acquireSharedInterruptibly(int arg) And acquireShared (int arg), which in response to the interrupt
boolean tryAcquireSharedNanos(int atg,long nanos) Increase the timeout limit
boolean release(int arg) Thread wakes exclusive synchronization state is released, which will be released after the synchronized state, the first node in the synchronous queue contains
boolean releaseShared(int arg) 共享式的释放同步状态
Colleaction getQueuedThreads() 获取等待在同步队列上的线程集合

//吐槽,我直接截图不好吗?????????????
同步器提供的模板方法基本上分3类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况。

队列同步器的实现分析

同步队列

同步器依赖内部的同步队列(FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点的线程唤醒,使其再次尝试获取同步状态。
在这里插入图片描述
节点时构成同步队列(等待队列)的基础,同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态(锁)的线程会把这个节点添加到队尾。同步器提供了一个CAS设置尾节点的方法:compareAndSetTail(Node expect,Node update),它需要传递当前线程"认为"的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立连接。

在这里插入图片描述

独占式同步状态获取与释放

通过调用同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感,也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&        保证线程安全的获取同步状态,如果同步状态获取失败
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))   把这个节点重新添加到队列尾部
            selfInterrupt();
    }
    
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);   将当前线程赋值给一个新的node
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;            将队尾的赋值给pred
        if (pred != null) {   如果队尾元素不为空,则这个没有拿到锁的node的前驱指向队尾的node
            node.prev = pred; 
            if (compareAndSetTail(pred, node)) {      调用CAS保证节点能够被线程安全添加
                pred.next = node;                
                return node;
            }
        }
        enq(node);   队列为空时执行enq
        return node;
    }    
    private Node enq(final Node node) {
        for (;;) {        死循环
            Node t = tail;
            if (t == null) { // Must initialize   队列为空就新建一个线程
                if (compareAndSetHead(new Node()))     通过CAS将节点设置成尾节点
                    tail = head;
            } else {
                node.prev = t;            
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

节点进入同步队列之后,就进入了一个自旋的过程,每个节点(或者说每个线程)都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中(并会阻塞节点的线程)。

	队列的头节点作为参数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; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在acquireQueued(final Node node,int arg)方法中,当前线程在“死循环”中尝试获取同步状态,而只有前驱节点是头节点才能获取同步状态,原因有两个
(1)头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否为头节点。
(2)维护同步队列的FIFO原则。该方法,节点自旋获取同步状态的行为

在这里插入图片描述
由于非首系欸但线程前驱节点出队列或者被中断而从等待状态返回,随后检查自己的前驱是否为头节点,如果是则尝试获取同步状态。节点和节点之间在循环检查的过程中基本不相互通信,而是简单地判断自己地前驱是否为头节点,这样就是使得节点地释放规则符合FIFO,并且也便于过早通知地处理(过早通知是指前驱节点不是头节点地线程由于中断而被唤醒)
独占式同步状态获取流程,acquier(int arg)方法调用流程
在这里插入图片描述
当前同步器队列中的头节点(线程)会通过CAS(自旋)获取锁,当前线程获取同步状态并执行了相应逻辑之后就需要释放同步状态,使得后续节点能够继续获取同步状态。通过调用同步器的release(int asg)方法可以释放同步状态,该方法释放了同步状态之后,会唤醒其后继节点(进而使后继节点重新尝试获取同步状态)

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);      来唤醒处于等待状态的线程
            return true;
        }
        return false;
    }

独占式同步状态获取和释放过程总结
在获取同步状态时,同步器维护了一个同步队列,获取同步状态失败的线程会同步CAS(自旋)重新插入到同步器的队尾;出队列的条件是前驱节点为头节点且成功获取了锁。在释放同步状态,同步器会将头节点的后继节点唤醒。

共享式同步状态获取与释放

共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。以文件读写为例,如果一个册灰姑娘徐在对文件进行读写操作,那么这一时刻对于该文件的写操作均被阻塞,而读操作可以进行。写操作要求对资源的独占式访问,而读操作可以是共享式访问。
同步器的acquireShared(int arg)方法可以共享式地获取同步状态

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)       没有获取到同步状态,调用doAcquireShared
            doAcquireShared(arg);
    }
    
    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);         
                    if (r >= 0) {              
                        setHeadAndPropagate(node, r);       
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    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)) {        通过CAS算法保证队尾元素能够正确的插入队列
                pred.next = node;
                return node;
            }
        }
        enq(node);                     队尾元素为空,就调用enq重新创建一个线程设置成队尾元素
        return node;
    }    

同步器调用用tryAcquireShared(int arg)方法尝试获取同步状态,tryAcquireShared(int arg)方法返回值为int类型,当返回值大于等于0时,表示能够获取到同步状态。
因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是: tryAcquireShared(int arg)方法返回值大于等于0。
可以看到,在doAcquireShared(int arg)方法的自旋过程中,如果当前节点的前驱为头节点时,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中退出。

共享式获取也需要释放同步状态

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {         释放成功
            doReleaseShared();               唤醒后续处于等待状态的节点
            return true;
        }
        return false;
    }

独占式超时获取同步状态

通过调用同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超时获取同步状 态,即在指定的时间段内获取同步状态,如果获取到同步状态则返回true,否则,返回false。该 方法提供了传统Java同步操作(比如synchronized关键字)所不具备的特性。

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;          
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在这里插入图片描述

发布了24 篇原创文章 · 获赞 1 · 访问量 541

Guess you like

Origin blog.csdn.net/qq_45366515/article/details/105161292