AQS

1.简介

AQS的全称是AbstractQueuedSynchronizer(AQS)。抽象的队列式同步器,AQS定义了一套多线程访问共享资源的同
步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock、Semaphore、CountDownLatch。

2.unsafe

java中的魔法类sun.misc.Unsafe,为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普
通用户使用。
2.1 获取Unsafe对象

查看Unsafe的源码我们会发现它提供了一个getUnsafe()的静态方法,但是调用这个方法会抛出一个SecurityException异常,这是因为Unsafe仅供java内部类使用,外部类不应该使用它。
在这里插入图片描述
查看源码,我们发现它有一个属性叫theUnsafe,我们直接通过反射拿到它即可。
在这里插入图片描述
在这里插入图片描述

2.2 CompareAndSwap操作
JUC下面大量使用了CAS操作,它们的底层是调用的Unsafe的CompareAndSwapXXX()方法。这种方式广泛运用于无
锁算法,与java中标准的悲观锁机制相比,它可以利用CAS处理器指令提供极大的加速。
public class Counter {
    //volatile修饰计数器,保证对其他线程可见
    private volatile int count = 0;
    //偏移量
    private static Long offset;
    
    private static Unsafe unsafe;
    
    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");               
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public void increment() {
        //旧值
        int old = count;
        //自增失败则自旋
        while (!unsafe.compareAndSwapInt(this, offset, old, old+1)) {
            old = count;
        }
    }
    
    public int getCount() {
        return count;
    }
}
2.3 park/unpark
JVM在上下文切换的时候使用了Unsafe中的park()和unpark()方法,对线程进行阻塞和唤醒。当一个线程正在等待
某个操作时,JVM调用Unsafe的park()方法来阻塞此线程。当阻塞中的线程需要再次运行时,JVM调用Unsafe
unpark()方法来唤醒此线程。

在这里插入图片描述

2.4 Unsafe类能做什么
  • 1.实例化一个类
  • 2.修改私有字段的值
  • 3.使用堆外内存
  • 4.CAS操作
  • 5.阻塞/唤醒线程

实例化一个对象的方法:共6种

  • 1.通过构造方法实例化一个类
  • 2.通过Class实例化一个类,究其本质也是反射
  • 3.通过反射实例化一个类
  • 4.通过克隆实例化一个类
  • 5.通过反序列化实例化一个类
  • 6.通过Unsafe实例化一个类
    在这里插入图片描述
    反序列化实例对象:
    在这里插入图片描述

3.AQS核心源码

3.1核心内部类

Node是AQS的内部类,AQS维护的CLH队列中,每一个节点就是一个Node,代表着需要获取锁的线程。
static final class Node {
    // 标识一个节点是共享模式
    static final Node SHARED = new Node();
    // 标识一个节点是互斥模式
    static final Node EXCLUSIVE = null;
    // 标识线程已取消
    static final int CANCELLED =  1;
    // 标识后继节点需要唤醒
    static final int SIGNAL    = -1;
    // 标识线程等待在一个条件上
    static final int CONDITION = -2;
    // 标识后面的共享锁需要无条件的传播(共享锁需要连续唤醒读的线程)
    static final int PROPAGATE = -3;  
    // 当前节点保存的线程对应的等待状态
    volatile int waitStatus;
    // 前驱节点
    volatile Node prev;
    // 后继节点
    volatile Node next;
    // 当前节点保存的线程
    volatile Thread thread;
    // condition条件锁时使用或者标记共享模式
    Node nextWaiter;
    // 是否是共享模式
    final boolean isShared() {
        return nextWaiter == SHARED;
    }
    // 获取前驱节点
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    // 节点的构造方法
    Node() {    // Used to establish initial head or SHARED marker
    }
    // 节点的构造方法
    Node(Thread thread, Node mode) {     // Used by addWaiter
        // 把共享模式还是互斥模式存储到nextWaiter这个字段里面了
        this.nextWaiter = mode;
        this.thread = thread;
    }
    // 节点的构造方法
    Node(Thread thread, int waitStatus) { // Used by Condition
        // 等待的状态,在Condition中使用
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

3.2 CLH队列

	//队列头结点
 	private transient volatile Node head;
 	//队列尾节点
    private transient volatile Node tail;
    //控制加锁解锁的状态变量
    private volatile int state;

Unsafe类的作用

	//获取Unsafe对象
 	private static final Unsafe unsafe = Unsafe.getUnsafe();
 	//状态变量的偏移量
    private static final long stateOffset;
    //CLH队列头结点的偏移量
    private static final long headOffset;
    //CLH队列尾节点的偏移量
    private static final long tailOffset;
    //当前节点保存的线程等待状态的偏移量
    private static final long waitStatusOffset;
    //后继节点的偏移量(Node属性)
    private static final long nextOffset;
    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }
	
	/**
	 * CAS更新状态变量
	 */
	protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    /**
     * CAS操作头结点
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    /**
     * CAS操作尾节点
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    /**
     * CAS操作结点等待状态
     */
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

    /**
     * CAS操作后继节点
     */
    private static final boolean compareAndSetNext(Node node,
                                                   Node expect,
                                                   Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }

典型的双链表结构:定义了一个状态变量和一个队列,状态变量用来控制加锁解锁,队列用来放置等待的线程。CLH是一个先进先出的线程等待队列,线程在获取锁失败被阻塞的时候就会进入此队列。
在这里插入图片描述

3.3 入CLH队列

  1. 调用addWaiter(Node mode)方法将node节点添加到CLH队列尾部,利用CAS操作保证线程安全添加。
  2. 如果添加失败,调用enq(node)方法一直尝试添加直到成功为止,利用CAS操作保证线程安全添加。
	private Node addWaiter(Node mode) {
		//新建一个Node节点
        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;
            }
        }
        //多次尝试
        enq(node);
        return node;
    }
	private Node enq(final Node node) {
		//一直尝试直到成功为止
        for (;;) {
            Node t = tail;
            //如果尾节点不存在,设置为头结点
            if (t == null) { 
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

3.4 出CLH队列

CLH同步队列遵循FIFO,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取
同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev
即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。

4.锁的获取和释放

AbstractQueuedSynchronizer本质上是一个抽象类,它是构建Java同步组件的基础。AQS的设计模式采用的模板
方法模式,子类通过继承的方式,实现它的抽象方法来管理同步状态。那么子类实现一个同步器需要实现哪些方法
呢?主要是三类:独占式获取和释放同步状态、共享式获取和释放同步状态、查询同步队列中的等待线程情况。
	// 互斥模式下使用:尝试获取锁
	protected boolean tryAcquire(int arg) {
	    throw new UnsupportedOperationException();
	}
	// 互斥模式下使用:尝试释放锁
	protected boolean tryRelease(int arg) {
	    throw new UnsupportedOperationException();
	}
	// 共享模式下使用:尝试获取锁
	protected int tryAcquireShared(int arg) {
	    throw new UnsupportedOperationException();
	}
	// 共享模式下使用:尝试释放锁
	protected boolean tryReleaseShared(int arg) {
	    throw new UnsupportedOperationException();
	}
	// 如果当前线程独占着锁,返回true
	protected boolean isHeldExclusively() {
	    throw new UnsupportedOperationException();
	}

4.1 独占式

线程获取同步状态时如果获取失败,则加入CLH同步队列,通过自旋的方式不断获取同步状态,但是在自旋的过程中则需要判断当前线程是否需要阻塞,其主要方法在acquireQueued()

	public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
	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);
        }
    }
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		//前驱节点的等待状态
        int ws = pred.waitStatus;
        //状态为signal,表示当前线程处于等待状态,直接返回true
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            //前驱节点状态>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();
    }
  1. tryAcquire:去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。
  2. addWaiter:如果tryAcquire返回FALSE(获取同步状态失败),则调用该方法将当前线程加入到CLH同步队列尾部。
  3. acquireQueued:当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;并且返回当前线程在等待过程中有没有中断过。
  4. selfInterrupt:产生一个中断。

在这里插入图片描述

4.2 独占式获取响应中断

AQS提供了acquire(int arg)方法以供独占式获取同步状态,但是该方法对中断不响应,对线程进行中断操作后,该
线程会依然位于CLH同步队列中等待着获取同步状态。为了响应中断,AQS提供了acquireInterruptibly(int arg)
方法,该方法在等待获取同步状态时,如果当前线程被中断了,会立刻响应中断抛出异常InterruptedException。
	public final void acquireInterruptibly(int arg)
            throws InterruptedException {
            //先判断线程是否被中断过
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
 private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //线程如果中断,直接抛异常
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

doAcquireInterruptibly(int arg)方法与acquire(int arg)方法仅有两个差别

  • 1.方法声明抛出InterruptedException异常。
  • 2.在中断方法处不再是使用interrupted标志,而是直接抛出InterruptedException异常。

4.3 独占式超时获取

AQS除了提供上面两个方法外,还提供了一个增强版的方法:tryAcquireNanos(int arg,long nanos)。它除了响应中断外,还有超时控制。即如果当前线程没有在指定时间内获取同步状态,则会返回false,否则返回true。如下:

	public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
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;
                  //如果没有超时,则等待nanosTimeout纳秒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)              
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

4.4 独占式同步状态释放

当线程获取同步状态后,执行完相应逻辑后就需要释放同步状态。AQS提供了**release(int arg)**方法释放同步状态:

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	//唤醒后继节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
private void unparkSuccessor(Node node) {
        //如果当前节点的等待状态<0,CAS更新为0
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //找到当前节点的后继节点,并唤醒
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
        	//唤醒
            LockSupport.unpark(s.thread);
    }

4.5 共享式同步状态获取

共享式与独占式的最主要区别在于同一时刻独占式只能有一个线程获取同步状态,而共享式在同一时刻可以有多个
线程获取同步状态。

共享式同步状态获取

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
private void doAcquireShared(int arg) {
		//共享node加到CLH队列尾部
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	//获取当前节点的前驱
                final Node p = node.predecessor();
                if (p == head) {
                	//r>=0则表示获取共享锁成功
                    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);
        }
    }

共享式同步状态释放

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

参考

死磕 java魔法类之Unsafe解析
死磕 java同步系列之AQS起篇

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值