*AQS详解

前言

AQS即AbstractQueuedSynchronizer ,提供加锁解锁的一套模板,具体实现细节由子类实现,可以通过"三板斧"的辅助概念来理解:

  • 状态 status
  • 队列 queue
  • cas

状态 state

private volatile int state;//同步状态变量

state为整个AQS的核心,是全局共享的一个状态,为保证其修改的可见性,用volatile修饰。state在不同的子类中有不同的含义。

以ReentrantLock为例,state表示该锁被线程重入的次数:当state=0时表示该锁不被任何线程持有;state=1表示当前线程持有该锁1次;state>1表示当前线程重入锁的次数;

state有三种访问方式:getState();setState();compareAndSetState(),都是原子操作。

队列

同步等待队列是一个Node节点的双向链表来实现,队列采用悲观锁的思想,即当前线程需要获取锁时就有其他线程也来获锁。因此,它会把当前线程包装成一个Node节点,放入等待队列中,当一定条件满足后,再从等待队列中移除。

  • Node节点

volatile int waitStatus;//当前节点等待的状态

volatile Node prev;//前驱

volatile Node next;//后继

volatile Thread thread;//当前节点的线程

waitStatus的状态有5种:

static final int CANCELLED(1);//当前节点已取消调度,当timeout或者中断会触发更新成改状态,终态

static final int SINGAL(-1);//后继节点在等待当前节点唤醒,当后继结点加入时会触发更新为改状态

static final int CONDITION(-2);//结点等待在Condition上,当其他线程调用了Condition的signal()方 
    //法后,CONDITION 状态的节点将从等待队列转移到同步队列中,等待获取同步锁

static final int PROPAGATE(-3);//共享模式下,前驱结点不仅会唤醒后继结点,也有可能唤醒后继的后继

0;默认状态
  • 双向CLH链表
private transient volatile Node head;

private transient volatile Node tail;

head结点:是一个哑结点(dummy node),它不代表任何线程,一次head所指向的Node的thread永远是null。只有从次结点往后的所有结点才表示所有等待锁的线程。

AQS定义了两种资源共享方式:独占式(Exclusive)和共享式(Share)。

  •  独占式:只有一个线程能执行,具体的Java实现有ReentrantLock。
  •  共享式:多个线程可同时执行,具体的Java实现有Semaphore和CountDownLatch。

AQS只是一个框架,只定义了一个接口,具体资源的获取、释放都交由自定义同步器去实现。不同的自定义同步器争用共享资源的方式也不同,自定义同步器在实现时只需实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护,如获取资源失败入队、唤醒出队等,AQS已经在顶层实现好,不需要具体的同步器再做处理。

 独占:

ReentrantLock对AQS的独占方式实现为:

  • ReentrantLock中的state初始值为0时表示无锁状态。
  • 在线程执行tryAcquire()获取该锁后ReentrantLock中的state+1,这时该线程独占ReentrantLock锁,其他线程在通过tryAcquire()获取锁时均会失败,直到该线程释放锁后state再次为0,其他线程才有机会获取该锁。
  • 该线程在释放锁之前可以重复获取此锁,每获取一次便会执行一次state+1,因此ReentrantLock也属于可重入锁。但获取多少次锁就要释放多少次锁,这样才能保证state最终为0。
  • 如果获取锁的次数多于释放锁的次数,则会出现该线程一直持有该锁的情况;如果获取锁的次数少于释放锁的次数,则运行中的程序会报锁异常。
  • ReentrantLock 默认是非公平的,提高吞吐

共享:

CountDownLatch对AQS的共享方式实现为:

  • CountDownLatch将任务分为N个子线程去执行,将state也初始化为N, N与线程的个数一致,N个子线程是并行执行的,
  • 每个子线程都在执行完成后countDown()一次,state会执行CAS操作并减1。
  • 在所有子线程都执行完成(state=0)时会unpark()主线程,然后主线程会从await()返回,继续执行后续的动作。

unparkSuccessor 方法中for循环从tail开始而不是head

这个要看加入的地方

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

新节点pre指向tail,tail指向新节点,这里后继指向前驱的指针是由CAS操作保证线程安全的。而cas操作之后t.next=node之前,可能会有其他线程进来。所以出现了问题,从尾部向前遍历是一定能遍历到所有的节点。

ABA问题

CAS 并不是万能的,CAS 更新有 ABA 问题。即 T1 读取内存变量为 A ,T2 修改内存变量为 B ,T2 修改内存变量为 A ,这时 T1 再 CAS 操作 A 时是可行的。但实际上在 T1 第二次操作 A 时,已经被其他线程修改过了。

AtomicStampedReference

对于 ABA 问题,比较有效的方案是引入版本号,内存中的值每发生一次变化,版本号都 +1;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。 AtomicStampedReference 便是使用版本号来解决ABA问题的。类似的还有 AtomicMarkableReference , AtomicStampedReference 是使用 pair 的 int stamp 作为计数器使用, AtomicMarkableReference 的 pair 使用的是 boolean mark

ReentrantReadWriteLock

线程进入读锁的前提条件:

  • 没有其他线程的写锁,
  • 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

线程进入写锁的前提条件:

  • 没有其他线程的读锁
  • 没有其他线程的写锁

而读写锁的特性:

(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。

(2)重进入:读锁和写锁都支持线程重进入。

(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

说明:Sync抽象类继承自AQS抽象类,Sync类提供了对ReentrantReadWriteLock的支持。

Sync类内部存在两个内部类,分别为HoldCounter和ThreadLocalHoldCounter,其中HoldCounter主要与读锁配套使用,其中,HoldCounter源码如下。

        static final class HoldCounter {
            int count = 0;
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());
        }

        /**
         * ThreadLocal subclass. Easiest to explicitly define for sake
         * of deserialization mechanics.
         */
        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值