JUC-并发编程-05-lock原理-AQS

本篇主要讲了lock的原理其实就是AQS,源码为证:

    ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer。

   Sync又有两个子类:

static final class NonfairSync extends Sync
static final class FairSync extends Sync

显然是为了支持公平锁和非公平锁而定义,默认情况下为非公平锁。非公平锁tryAcquire的流程是:检查state字段,若为0,表示锁未被占用,那么尝试占用,若不为0,检查当前锁是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数。如果以上两点都没有成功,则获取锁失败,返回false。

解决多线程的并发安全问题,java无非就是加锁,具体就是两个方法:

  1. Synchronized(java自带的关键字)
  2.  lock 可重入锁 (可重入锁这个包java.util.concurrent.locks 底下有两个接口,分别对应两个类实现了这个两个接口: 

           (a)lock接口, 实现的类为:ReentrantLock类 可重入锁;

           (b)readwritelock接口,实现类为:ReentrantReadWriteLock 读写锁)

读写锁特点:

a)多个读者可以同时进行读
b)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
c)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

总结来说,Lock和synchronized有以下几点不同:

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2. 当synchronized块结束时,会自动释放锁,lock一般需要在finally中自己释放。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3. lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断。
  4. lock可以通过trylock来知道有没有获取锁,而synchronized不能; 
  5. 当synchronized块执行时,只能使用非公平锁,无法实现公平锁,而lock可以通过new ReentrantLock(true)设置为公平锁,从而在某些场景下提高效率。
  6. Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
  7. synchronized 锁类型 可重入 不可中断 非公平 而 lock 是: 可重入 可中断  可公平,非公平

1、ReentrantLock(可重入锁) 

可重入的意思是某一个线程是否可多次获得一个锁,在继承的情况下,如果不是可重入的,那就形成死锁了,比如递归调用自己的时候;如果不能可重入,每次都获取锁不合适,比如synchronized就是可重入的,ReentrantLock也是可重入的。

1.1 为什么要重入

如果线程A继续再次获得这个锁呢?比如一个方法是synchronized,递归调用自己,那么第一次已经获得了锁,第二次调用的时候还能进入吗? 直观上当然需要能进入.这就要求必须是可重入的.可重入锁又叫做递归锁,不然就死锁了。为每个锁关联一个获取计数器和一个所有者线程,当计数值为0的时候,这个所就没有被任何线程只有.当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,技术值将递增,退出一次同步代码块,计算值递减,当计数值为0时,这个锁就被释放。

ReentrantLock里面有实现:我们看下代码:

/**
     * The synchronization state.
     */
    private volatile int state;

state表示当前拿锁的总数。我们看到他是volatile修饰。保证了可见性和有序性。那么如何保证原子性呢?我们看下下面tryLock()的源码:

final boolean nonfairTryAcquire(int acquires) {
            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;
        }
  1. 如果state==0表示当前没有线程获取锁,可以直接获取。这里有一个cas方式获取锁保证独占式获取。如果获取不到就返回false;
  2. 如果当前线程就是已经拿到锁的线程,那么就将state进行加一然后返回true,反之返回false;

注:如果将state最值只能是1,那么就是不可重入锁了。

ReentrantLock 类是唯一实现了Lock的类 ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁投票定时锁等候可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。

用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!!  

1.2 Lock 接口

方法如下:

public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
  • 获取锁:lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()
  • 释放锁:unLock()

在Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?

  1. lock():是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。
  2. tryLock():是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
  3. tryLock(long time, TimeUnit unit):和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。使用如下:
    Lock lock = ...;
    if(lock.tryLock()) {
    try{
    //处理任务
    }catch(Exception ex){
    
    }finally{
    lock.unlock(); //释放锁
    } 
    }else {
    //如果不能获取锁,则直接做其他事情
    }

     

  4.  lockInterruptibly():比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明InterruptedException。 注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。   

2、lock()与unlock()实现原理

ReentrantLock是Lock的默认实现之一。那么lock()和unlock()是怎么实现的呢?首先我们要弄清楚几个概念:

  • 可重入锁。可重入锁是指同一个线程可以多次获取同一把锁。ReentrantLock和synchronized都是可重入锁。
  • 可中断锁。可中断锁是指线程尝试获取锁的过程中,是否可以响应中断。synchronized是不可中断锁,而ReentrantLock则提供了中断功能。
  • 公平锁与非公平锁。公平锁是指多个线程同时尝试获取同一把锁时,获取锁的顺序按照线程达到的顺序,而非公平锁则允许线程“插队”。synchronized是非公平锁,而ReentrantLock的默认实现是非公平锁,但是也可以设置为公平锁。
  • CAS操作(CompareAndSwap)。CAS操作简单的说就是比较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。” Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。

ReentrantLock提供了两个构造器,分别是:

public ReentrantLock() {
    sync = new NonfairSync();
}
 
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

 默认构造器初始化为NonfairSync对象,即非公平锁,而带参数的构造器可以指定使用公平锁和非公平锁。由lock()和unlock的源码可以看到,它们只是分别调用了sync对象的lock()和release(1)方法。

Sync是ReentrantLock的内部类,它的结构如下:

可以看到Sync扩展了AbstractQueuedSynchronizer。

2.1 NonfairSync

我们从源码层面去学习非公平锁的获取和释放的过程。

2.1.1 lock()

源码如下:

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
  • 首先使用了CAS操作,判断state是否是0,如果是0置为1,并且设置当前的线程为该锁的独占线程。当多个线程同时尝试占用一个锁时,CAS操作只能保证一个线程操作成功。当多个线程同时尝试占用同一个锁时,CAS操作只能保证一个线程操作成功,剩下的只能乖乖的去排队啦。怎么排队?往下看。

 “非公平”即体现在这里,如果占用锁的线程刚释放锁,state置为0,而排队等待锁的线程还未唤醒时,新来的线程就直接抢占了该锁,那么就“插队”了(请注意此处的非公平锁是指新来的线程跟队列头部的线程竞争锁,队列其他的线程还是正常排队,百度面试题)。

  • 如果CAS操作失败,那么就会走到else里,调用acquire方法。

代码如下:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • 第一步,尝试获取锁,调用tryAcquire方法。代码如下:
    final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程
        final Thread current = Thread.currentThread();
        //获取state变量值
        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");
            // 更新state值为新的重入次数
            setState(nextc);
            return true;
        }
        //获取锁失败
        return false;
    }

    非公平锁tryAcquire的流程是:检查state字段,若为0,表示锁未被占用,那么尝试占用,若不为0,检查当前锁是否被自己占用,若被自己占用,则更新state字段,表示重入锁的次数。如果以上两点都没有成功,则获取锁失败,返回false。

  • 第二步,入队。调用addWaiter(Node.EXCLUSIVE) 方法 代码如下:

    /**
     * 将新节点和当前线程关联并且入队列
     * @param mode 独占/共享
     * @return 新节点
     */
    private Node addWaiter(Node mode) {
        //初始化节点,设置关联线程和模式(独占 or 共享)
        Node node = new Node(Thread.currentThread(), mode);
        // 获取尾节点引用
        Node pred = tail;
        // 尾节点不为空,说明队列已经初始化过
        if (pred != null) {
            node.prev = pred;
            // 设置新节点为尾节点
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 尾节点为空,说明队列还未初始化,需要初始化head节点并入队新节点
        enq(node);
        return node;
    }

    假设多线程下有个线程走到了enq方法,我们看看源码:

    /**
     * 初始化队列并且入队新节点
     */
    private Node enq(final Node node) {
        //开始自旋
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                // 如果tail为空,则新建一个head节点,并且tail指向head
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                // tail不为空,将新节点入队
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

    这里体现了经典的自旋+CAS组合来实现非阻塞的原子操作。由于compareAndSetHead的实现使用了unsafe类提供的CAS操作,所以只有一个线程会创建head节点成功。假设有两个线程B,C。线程B成功,之后B、C开始第二轮循环,此时tail已经不为空,两个线程都走到else里面。假设B线程compareAndSetTail成功,那么B就可以返回了,C由于入队失败还需要第三轮循环。最终所有线程都可以成功入队。当B、C入等待队列时,此时的AQS队列如下:

  • 第三步,挂起(A线程获取锁)。B和C相继执行acquireQueued(final Node node, int arg)。这个方法让已经入队的线程尝试获取锁,若失败则会被挂起。 代码如下:
    /**
     * 已经入队的线程尝试获取锁
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true; //标记是否成功获取锁
        try {
            boolean interrupted = false; //标记线程是否被中断过
            for (;;) {
                final Node p = node.predecessor(); //获取前驱节点
                //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node); // 获取成功,将当前节点设置为head节点
                    p.next = null; // 原head节点出队,在某个时间点被GC回收
                    failed = false; //获取成功
                    return interrupted; //返回是否被中断过
                }
                // 判断获取失败后是否可以挂起,若可以则挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    // 线程若被中断,设置interrupted为true
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    code里的注释已经很清晰的说明了acquireQueued的执行流程。假设B和C在竞争锁的过程中A一直持有锁,那么它们的tryAcquire操作都会失败,因此会走到第2个if语句中。我们再看下shouldParkAfterFailedAcquire和parkAndCheckInterrupt都做了哪些事吧。代码如下:

    /**
     * 判断当前线程获取锁失败之后是否需要挂起.
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //前驱节点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            // 前驱节点状态为signal,返回true
            return true;
        // 前驱节点状态为CANCELLED
        if (ws > 0) {
            // 从队尾向前寻找第一个状态不为CANCELLED的节点
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 将前驱节点的状态设置为SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
      
    /**
     * 挂起当前线程,返回线程中断状态并重置
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

    线程入队后能够挂起的前提是,它的前驱节点的状态为SIGNAL,它的含义是“Hi,前面的兄弟,如果你获取锁并且出队后,记得把我唤醒!”。所以shouldParkAfterFailedAcquire会先判断当前节点的前驱是否状态符合要求,若符合则返回true,然后调用parkAndCheckInterrupt,将自己挂起。如果不符合,再看前驱节点是否>0(CANCELLED),若是那么向前遍历直到找到第一个符合要求的前驱,若不是则将前驱节点的状态设置为SIGNAL。

    也就是说当队列头部的线程执行完了之后,这个线程会调用后面的队列的第一个线程(百度面试)。 整个流程中,如果前驱结点的状态不是SIGNAL,那么自己就不能安心挂起,需要去找个安心的挂起点,同时可以再尝试下看有没有机会去尝试竞争锁。最终队列可能会如下图所示:

线程B和C都已经入队,并且都被挂起。当线程A释放锁的时候,就会去唤醒线程B去获取锁啦。

2.1.2 unlock()

unlock相对于lock就简单很多。源码如下 :

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

如果理解了加锁的过程,那么解锁看起来就容易多了。流程大致为先尝试释放锁,若释放成功,那么查看头结点的状态是否为SIGNAL,如果是则唤醒头结点的下个节点关联的线程,如果释放失败那么返回false表示解锁失败。这里我们也发现了,每次都只唤起头结点的下一个节点关联的线程

最后我们再看下tryRelease的执行过程 :

/**
 * 释放当前线程占用的锁
 * @param releases
 * @return 是否释放成功
 */
protected final boolean tryRelease(int releases) {
    // 计算释放后state值
    int c = getState() - releases;
    // 如果不是当前线程占用锁,那么抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        // 锁被重入次数为0,表示释放成功
        free = true;
        // 清空独占线程
        setExclusiveOwnerThread(null);
    }
    // 更新state值
    setState(c);
    return free;
}

这里入参为1。tryRelease的过程为:当前释放锁的线程若不持有锁,则抛出异常。若持有锁,计算释放后的state值是否为0,若为0表示锁已经被成功释放,并且则清空独占线程,最后更新state值,返回free。 

2.1.3 小结

用一张流程图总结一下非公平锁的获取过程。

2.2 FairSync

公平锁和非公平锁不同之处在于,公平锁在获取锁的时候,不会先去检查state状态,而是直接执行aqcuire(1),这里不再赘述。

3、超时机制

在ReetrantLock的tryLock(long timeout, TimeUnit unit) 提供了超时获取锁的功能。它的语义是在指定的时间内如果获取到锁就返回true,获取不到则返回false。这种机制避免了线程无限期的等待锁释放。那么超时的功能是怎么实现的呢?我们还是用非公平锁为例来一探究竟。

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

还是调用了内部类里面的方法。我们继续向前探究  

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

这里的语义是:如果线程被中断了,那么直接抛出InterruptedException。如果未中断,先尝试获取锁,获取成功就直接返回,获取失败则进入doAcquireNanos。tryAcquire我们已经看过,这里重点看一下doAcquireNanos做了什么。

/**
 * 在有限的时间内去竞争锁
 * @return 是否获取成功
 */
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    // 起始时间
    long lastTime = System.nanoTime();
    // 线程入队
    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;
            }
            // 如果已经超时,返回false
            if (nanosTimeout <= 0)
                return false;
            // 超时时间未到,且需要挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                // 阻塞当前线程直到超时时间到期
                LockSupport.parkNanos(this, nanosTimeout);
            long now = System.nanoTime();
            // 更新nanosTimeout
            nanosTimeout -= now - lastTime;
            lastTime = now;
            if (Thread.interrupted())
                //相应中断
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

 doAcquireNanos的流程简述为:线程先入等待队列,然后开始自旋,尝试获取锁,获取成功就返回,失败则在队列里找一个安全点把自己挂起直到超时时间过期。这里为什么还需要循环呢?因为当前线程节点的前驱状态可能不是SIGNAL,那么在当前这一轮循环中线程不会被挂起,然后更新超时时间,开始新一轮的尝试 。

4、读写锁

与互斥锁定相比,读-写锁定允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程) 

从理论上讲,与互斥锁定相比,使用读-写锁定所允许的并发性增强将带来更大的性能提高。

在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。——例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁定的理想候选者。 

5、线程之间通信

Condition可以替代传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

——为什么方法名不直接叫wait()/notify()/nofityAll()?因为Object的这几个方法是final的,不可重写!

传统线程的通信方式,Condition都可以实现。

注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。 

Condition的强大之处在于它可以为多个线程间建立不同的Condition

看JDK文档中的一个例子:假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition实例来做到这一点。

——其实就是java.util.concurrent.ArrayBlockingQueue的功能

优点:
假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程。

6、AQS

上面我们学习了lock,其实也是对AQS有了初步的了解。首先我们看看源码里面的介绍。

   Provides a framework for implementing blocking locks and relate 
synchronizers (semaphores, events, etc) that rely on 
first-in-first-out (FIFO) wait queues.

翻译过来就是:提供一个框架来实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关的同步器(信号灯,事件等)。

  • 在同步组件的实现中,AQS是核心部分,同步组件的实现者通过使用AQS提供的模板方法实现同步组件语义。
  • AQS 则实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等等一些底层的实现处理。
  • AQS的核心也包括了这些方面:同步队列,独占式锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现,而这些实际上则是AQS提供出来的模板方法。
模板方法:
独占式获取
accquire
acquireInterruptibly
tryAcquireNanos
共享式获取
acquireShared
acquireSharedInterruptibly
tryAcquireSharedNanos
独占式释放锁
release
共享式释放锁
releaseShared
需要子类覆盖的流程方法
独占式获取  tryAcquire
独占式释放  tryRelease
共享式获取 tryAcquireShared
共享式释放  tryReleaseShared
这个同步器是否处于独占模式  isHeldExclusively
同步状态state:
getState:获取当前的同步状态
setState:设置当前同步状态
compareAndSetState 使用CAS设置状态,保证状态设置的原子性

在AQS有一个静态内部类Node,这是我们同步队列的每个具体节点。在这个类中有如下属性:

volatile int waitStatus; // 节点状态
volatile Node prev; // 当前节点的前驱节点 
volatile Node next; // 当前节点的后继节点 
volatile Thread thread; // 当前节点所包装的线程对象 
Node nextWaiter; // 等待队列中的下一个节点

6.1 节点的状态如下:

  • int INITIAL = 0; // 初始状态
  • int CANCELLED = 1; // 当前节点从同步队列中取消
  • int SIGNAL = -1; // 后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继 节点的线程继续运行。
  • int CONDITION = -2; // 节点在等待队列中,节点线程等待在Condition上,当其他线程对Condition调用了 signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中。
  • int PROPAGATE = -3; // 表示下一次共享式同步状态获取将会无条件地被传播下去。

 AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer,这个类只有一个变量:exclusiveOwnerThread,表示当前占用该锁的线程,并且提供了相应的get,set方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值