你了解Java中的同步器框架AQS吗?

AbstractQueuedSynchronizer

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements Serializable

概念

为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()setState(int)compareAndSetState(int, int)方法来操作以原子方式更新的 int 值。

应该将子类定义为非公共内部帮助器类,可用它们来实现其封闭类的同步属性。类 AbstractQueuedSynchronizer 没有实现任何同步接口。而是定义了诸如 acquireInterruptibly(int)之类的一些方法,在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法。

此类支持默认的独占模式和共享模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不"了解"这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock 中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。

此类通过支持独占模式的子类定义了一个嵌套的 AbstractQueuedSynchronizer.ConditionObject 类,可以将这个类用作 Condition实现。其中:

  • isHeldExclusively()方法将报告同步对于当前线程是否是独占的
  • 使用当前 getState() 值调用 release(int)方法则可以完全释放此对象
  • 如果给定保存的状态值,那么 acquire(int)方法可以将此对象最终恢复为它以前获取的状态

没有别的 AbstractQueuedSynchronizer 方法创建这样的条件,因此,如果无法满足此约束,则不要使用它。AbstractQueuedSynchronizer.ConditionObject的行为当然取决于其同步器实现的语义。

此类为内部队列提供了检查、检测和监视方法,还为 condition 对象提供了类似方法。可以根据需要使用用于其同步机制的 AbstractQueuedSynchronizer 将这些方法导出到类中。

此类的序列化只存储维护状态的基础原子整数,因此已序列化的对象拥有空的线程队列。需要可序列化的典型子类将定义一个 readObject 方法,该方法在反序列化时将此对象恢复到某个已知初始状态。

使用

为了将此类用作同步器的基础,需要适当地重新定义以下方法,这是通过使用 getState()setState(int)compareAndSetState(int, int)方法来检查和修改同步状态来实现的:

  • tryAcquire(int)

    试图在独占模式下获取对象状态。此方法应该查询是否允许它在独占模式下获取对象状态,如果允许,则获取它。 此方法总是由执行 acquire 的线程来调用。

    其定义了获取锁的一种方式,具体的逻辑判断等,由acquire(int)方法调用并做调度,如下:

            @Override
            protected boolean tryAcquire(int arg) {
                Thread thread = Thread.currentThread();
                int c = getState();
                if (c == 0) {
                    if (!hasQueuedPredecessors() && compareAndSetState(c, arg)) {
                        setExclusiveOwnerThread(Thread.currentThread());
                    }
                    return true;
                } else if (getExclusiveOwnerThread() == thread) {
                    int nextC = c + arg;
                    if (nextC < 0) {
                        throw new Error("Maximum lock count exceeded");
                    }
                    setState(nextC);
                    return true;
                }
                return false;
            }
    
  • tryRelease(int)

    试图设置状态来反映独占模式下的一个释放。此方法总是由正在执行释放的线程调用。

    其定义了释放锁的一种方式,具体的逻辑判断等,由release(int)方法调用并做调度,如下:

           @Override
            protected boolean tryRelease(int arg) {
                if (getExclusiveOwnerThread() != Thread.currentThread()) {
                    throw new IllegalMonitorStateException();
                }
    
                int c = getState() - arg;
                boolean free = false;
                if (c == 0) {
                    setExclusiveOwnerThread(null);
                    free = true;
                }
    
                setState(c);
                return free;
            }
    
  • tryAcquireShared(int)

    试图在共享模式下获取对象状态。此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它。 此方法总是由执行 acquire 线程来调用。如果此方法报告失败,则 acquire 方法可以将线程加入队列(如果还没有将它加入队列),直到其获得了其他某个线程释放了该线程的信号。

  • tryReleaseShared(int)

    试图设置状态来反映共享模式下的一个释放。 此方法总是由正在执行释放的线程调用。

  • isHeldExclusively()

    如果对于当前(正调用的)线程,同步是以独占方式进行的,则返回 true。

            @Override
            protected boolean isHeldExclusively() {
                return getExclusiveOwnerThread() == Thread.currentThread();
            }
    
  • AbstractOwnableSynchronizer.setExclusiveOwnerThread(Thread)

    设置当前拥有独占访问的线程。

默认情况下,每个方法都抛出UnsupportedOperationException。这些方法的实现在内部必须是线程安全的,通常应该很短并且不被阻塞。定义这些方法是使用此类的唯一受支持的方式。其他所有方法都被声明final,因为它们无法是各不相同的。

您也可以查找从 AbstractOwnableSynchronizer 继承的方法,用于跟踪拥有独占同步器的线程。鼓励使用这些方法,这允许监控和诊断工具来帮助用户确定哪个线程保持锁。

即使此类基于内部的某个 FIFO 队列,它也无法强行实施 FIFO 获取策略。独占同步的核心采用以下形式:

 Acquire:
     while (!tryAcquire(arg)) {
        enqueue thread if it is not already queued;
        possibly block current thread;
     }

 Release:
     if (tryRelease(arg))
        unblock the first queued thread;
 

因为要在加入队列之前检查线程的获取状况,所以新获取的线程可能闯入其他被阻塞的和已加入队列的线程之前。不过如果需要,可以内部调用一个或多个检查方法,通过定义 tryAcquiretryAcquireShared 来禁用闯入。特别是 getFirstQueuedThread() 没有返回当前线程的时候,严格的 FIFO 锁定可以定义 tryAcquire 立即返回 false。只有 hasQueuedThreads()返回 true 并且 getFirstQueuedThread 不是当前线程时,更好的非严格公平的版本才可能会立即返回 false;如果 getFirstQueuedThread 不为 null 并且不是当前线程,则产生的结果相同。出现进一步的变体也是有可能的。

对于默认闯入(也称为 greedyrenouncementconvoy-avoidance)策略,吞吐量和可伸缩性通常是最高的。尽管无法保证这是公平的或是无偏向的,但允许更早加入队列的线程先于更迟加入队列的线程再次争用资源,并且相对于传入的线程,每个参与再争用的线程都有平等的成功机会。此外,尽管从一般意义上说,获取并非“自旋”,它们可以在阻塞之前对用其他计算所使用的 tryAcquire 执行多次调用。在只保持独占同步时,这为自旋提供了最大的好处,但不是这种情况时,也不会带来最大的负担。如果需要这样做,那么可以使用“快速路径”检查来先行调用 acquire 方法,以这种方式扩充这一点,如果可能不需要争用同步器,则只能通过预先检查 hasContended()来确认这一点。

通过特殊化其同步器的使用范围,此类为部分同步化提供了一个有效且可伸缩的基础,同步器可以依赖于 int 型的 state、acquire 和 release 参数,以及一个内部的 FIFO 等待队列。这些还不够的时候,可以使用 atomic类、自己的定制 Queue 类和 LockSupport阻塞支持,从更低级别构建同步器。

实现公平可重入锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;

/**
 * 自定义可重入公平锁.
 */
public class CustomFairLock {

    private static final class Sync extends AbstractQueuedSynchronizer {

        /**
         * tryAcquire定义了获取锁的方式
         *
         * @param arg
         * @return
         */
        @Override
        protected boolean tryAcquire(int arg) {
            Thread thread = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors()
                        && compareAndSetState(0, arg)) {
                    setExclusiveOwnerThread(Thread.currentThread());
                }
                return true;
            } else if (getExclusiveOwnerThread() == thread) {
                int nextC = c + arg;
                if (nextC < 0) {
                    throw new Error("Maximum lock count exceeded");
                }
                setState(nextC);
                return true;
            }
            return false;
        }

        /**
         * 定义了释放锁的方式
         *
         * @param arg
         * @return
         */
        @Override
        protected boolean tryRelease(int arg) {
            int c = getState() - arg;
            if (getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            }
            boolean free = false;
            if (c == 0) {
                setExclusiveOwnerThread(null);
                free = true;
            }

            setState(c);
            return free;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
    }

    private Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);

    }

    public void tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    public boolean unlock() {
        return sync.release(1);
    }
}

ReentrantLock

对于ReentrantLock其实现公平锁和非公平锁是通过以下代码来区分的:

非公平锁tryAcquire():

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;                                            

公平锁tryAcquire():

final Thread current = Thread.currentThread();                 
int c = getState();                                            
if (c == 0) { 
  //唯一的区别在这里,公平锁优先判断队列中有无以等待的线程,而非公平的则不去判断,直接compareAndSetState状态
    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;                                                                                        

你可能感兴趣的:(你了解Java中的同步器框架AQS吗?)