手记

AQS详解

AQS:全称“AbstractQueuedSynchronizer”,直译过来是抽象的队列同步器,一般我们把它叫做AQS,java中大部分并发类都是通过它来实现线程同步。它内部定义了一个变量(volatile int state)和一个等待队列,前者表示加锁状态,后者在多线程情况下争用资源时被阻塞会进入等待队列。

源码解析

volatile int state

volatile 是一个关键字,在并发处理中也经常会用到,后面单独用一篇文章介绍。

(已去除源码中的注释,方便阅读)

private volatile int state;
protected final int getState() {
        return state;
	}
protected final void setState(int newState) {
        state = newState;
	}
protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

独占锁(下面会介绍)state初始为0,表示未锁定状态,A线程加锁成功则state+1,这样后面的线程尝试获取锁的时候就会失败,只有当A线程释放锁state=0时,后面的锁才有可能成功获取锁。如果是可重入锁,那么A线程再次获取锁的时候,state会累加,当然,释放锁也要一层一层释放,直到state=0。共享锁state初始为N,多个线程可以同时执行,每个线程执行完会state-1,直到state=0时,再调用主线程。

其中compareAndSetState方法,是原子操作,里面使用了unsafe中的compareAndSwapInt方法,这个Unsafe(不安全)类,听着就不靠谱,为什么这里会用到呢。其实不止在这里,在JUC(java.util.concurrent)包中,尤其是在CAS里大量的用到Unsafe,这是因为Java不能像c语言那样直接访问操作系统底层,但Unsafe类提供了硬件级别的原子操作,而且有很高的效率。那为什么官方不建议开发者使用这个类呢,感觉特地取个这名字(Unsafe)就是吓唬程序员,不要用。因为Unsafe中直接访问内存的方法中使用的内存不受JVM管理,也就不能被GC,需要手动管理,稍有不慎就可能导致内存泄漏。

队列操作

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

将节点插入队列,必要时进行初始化。

里面的for (;;),又是个骚操作,有人会问为啥不用while(true)呢,都是无限循环,而且while(true)看着就很舒服。经常看源码的同学,可能会发现,jdk源码中很多地方都是for(;;),至于为啥用这个,咱们以后有机会再讨论。

Node t = tail;(tail是尾节点,head是头节点)先定义一个尾部节点,如果尾节点是空的,说明队列是空的,这时就需要初始化,源码中特地加了注释// Must initialize,(这种直接在后面加注释的,咱们最好不要学,不符合阿里代码规范,但是Doug Lea是大大佬,阿里代码规范管不住他);初始化以后,接着循环进入else,node.prev = t;将当前节点挂在尾节点后面;(这种node.prev指向上个节点,t.next指向下个节点,这是双向队列的操作,还有单向队列,有兴趣的可以去了解一下)。

看这一个方法里就出现了compareAndSetHead和compareAndSetTail两个CAS操作,点进去发现还是Unsafe实现的,由此可见这个类在多线程中还是很重要的。

AQS中还有很多方法,咱们不一一介绍了,下面介绍几个和资源共享相关的方法。

资源共享方式

AQS定义了两种资源共享方式

  • 独占资源,只有一个线程能执行,如ReentrantLock
  • 共享资源,多个线程可以同时执行,如Semaphore/CountDownLatch。

独占模式采用tryAcquire-tryRelease实现,共享模式采用tryAcquireShared-tryReleaseShared实现。有一些特殊的锁两种模式都使用,比如ReentrantReadWriteLock(读写锁)。

  • isHeldExclusively():该线程是否独占资源。

    protected boolean isHeldExclusively() {
            throw new UnsupportedOperationException();
        }
    
  • tryAcquire(int):独占方式。尝试获取资源。

    protected boolean tryAcquire(int arg) {
            throw new UnsupportedOperationException();
        }
    
  • tryRelease(int):独占方式。尝试释放资源。

    protected boolean tryRelease(int arg) {
            throw new UnsupportedOperationException();
        }
    
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败; 0 表示成功,但没有剩余
    可用资源;正数表示成功,且有剩余资源。

    protected int tryAcquireShared(int arg) {
            throw new UnsupportedOperationException();
        }
    
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回
    true,否则返回 false。

    protected boolean tryReleaseShared(int arg) {
            throw new UnsupportedOperationException();
        }
    
0人推荐
随时随地看视频
慕课网APP