JUC-CAS和AQS
一、CAS
CAS, compare and swap比较并替换。 CAS有三个参数:需要读写的内存位值(V)、进行比较的预期原值(A)和拟写入的新值(B)。当且仅当V的值等于A时,CAS才会通过原子方式用新值B来更新V的值,否则不会执行任何操作。程序在在某个变更新时,会有一个校验逻辑——认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少。CAS是一项乐观的技术,它希望能成功地执行更新操作,并且如果有另一个线程在最近一次检查后更新了该变量,那么CAS能检测到这个错误。当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其他线程都将失败。但是,失败的线程并不会被挂起,而是被告知在这次竞争中失败,并可以多次尝试。这种灵活性可以减少与锁相关的活跃性风险,例如死锁,饥饿等问题。
AtomicInteger源码分析:
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; //是使用了Unsafe类来进行CAS操作,valueOffset表示的是value值的偏移地址,Unsafe会根据内存偏移地址获取数据的原值的, //偏移量对应指针指向该变量的内存地址。 private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { //内存偏移地址在静态代码块中通过Unsafe的objectFieldOffset方法获取。 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //使用volatile修饰,直接从共享内存中操作变量,保证多线程之间看到的value值是同一份 private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } //.... public final int get() { return value; //return操作是一个原子操作,具有原子性 } public final void set(int newValue) { value = newValue; //赋值操作是一个原子操作,具有原子性 } /** * 更新对应的数据,返回更新前的数据 * AtomicInteger atomicInteger = new AtomicInteger(2); * atomicInteger.incrementAndGet(i -> i * 3); */ public final int getAndUpdate(IntUnaryOperator updateFunction) { int prev, next; do { //从内存中读取修改前的值prev,并执行给定函数式计算修改后的值next; prev = get(); next = updateFunction.applyAsInt(prev); //调用compareAndSet修改value值(内部是调用了unsafe的compareAndSwapInt方法)。 //如果此时有其他线程也在修改这个value值,那么CAS操作就会失败,继续进入do循环重新获取新值,再次执行CAS直到修改成功。 } while (!compareAndSet(prev, next)); return prev; } }
Unsafe
Unsafe是实现CAS的核心类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。Unsafe类提供了硬件级别的原子操作(java里面没有指针,但java的sun.misc.Unsafe这个类是一个特殊,使得可以使用指针偏移操作,拥有了类似C语言指针一样操作内存空间的能力)。
public final class Unsafe { //获取对象o中给定偏移地址(offset)的值。以下相关get方法作用相同 public native int getInt(Object o, long offset); public native Object getObject(Object o, long offset); //在对象o的给定偏移地址存储数值x。以下set方法作用相同 public native void putInt(Object o, long offset, int x); public native void putObject(Object o, long offset, Object x); //获取给定内存地址的一个本地指针 public native long getAddress(long address); //在给定的内存地址处存放一个本地指针x public native void putAddress(long address, long x); ///------------------内存操作---------------------- //在本地内存分配一块指定大小的新内存,内存的内容未初始化;它们通常被当做垃圾回收。 public native long allocateMemory(long bytes); //重新分配给定内存地址的本地内存 public native long reallocateMemory(long address, long bytes); //释放给定地址的内存 public native void freeMemory(long address); //获取给定对象的偏移地址 public native long staticFieldOffset(Field f); public native long objectFieldOffset(Field f); //------------------数组操作--------------------------------- //获取给定数组的第一个元素的偏移地址 public native int arrayBaseOffset(Class<?> arrayClass); //获取给定数组的元素增量地址,也就是说每个元素的占位数 public native int arrayIndexScale(Class<?> arrayClass); ///--------------------锁指令(synchronized)------------------------------- //对象加锁 public native void monitorEnter(Object o); //对象解锁 public native void monitorExit(Object o); public native boolean tryMonitorEnter(Object o); //解除给定线程的阻塞 public native void unpark(Object thread); //阻塞当前线程 public native void park(boolean isAbsolute, long time); // CAS public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); //.... }
二、AbstractQueuedSynchronizer
AbstractQueuedSynchronizer是一个用于构建锁和同步器的框架,许多同步器都可以通过AQS很容易并且高效地构造出来,如常用的ReentrantLock、CountDownLatch等。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { //.... static final class Node { /** 标识节点当前在共享模式下 */ static final Node SHARED = new Node(); /** 标识节点当前在独占模式下 */ static final Node EXCLUSIVE = null; /** 值为1,当前节点由于超时或中断被取消。 */ static final int CANCELLED = 1; /** 后继节点在等待当前节点唤醒 */ static final int SIGNAL = -1; /** 等待在 condition 上 */ static final int CONDITION = -2; /** * 状态需要向后传播,针对共享锁 */ static final int PROPAGATE = -3; /** * 状态,默认 0 上面列出的几个常量状态 0代表没有被占用 */ volatile int waitStatus; /** * 前驱节点 */ volatile Node prev; /** * 后继节点 */ volatile Node next; /** * The thread that enqueued this node. Initialized on * construction and nulled out after use. */ volatile Thread thread; //... } /** * 队列维护的state值, */ private volatile int state; /** * 队列维护的state值内存地址的偏移量,通过unsafe类修改(CAS) */ private static final long stateOffset; }
同步队列其内部都是一个双向链表结构,主要的内容包含thread + waitStatus + pre + next
以ReentrantLock为例,进行代码阅读:
1、ReentrantLock下面有三个内部类:Sync同步器,FairSync同步器, NonfairSync同步器。
2、AQS(AbstractQueuedSynchronizer)继承AOS(AbstractOwnableSynchronizer)
2、Sync继承AQS(AbstractQueuedSynchronizer:设置和获取独占锁的拥有者线程)
3、NonfairSync(非公平锁)、FairSync(公平锁)分别继承Sync
public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; //ReentrantLock 在内部用了内部类 Sync 来管理锁,所以真正的获取锁和释放锁是由 Sync 的实现类来控制的。 abstract static class Sync extends AbstractQueuedSynchronizer { //.... } /** * 非公平锁 */ static final class NonfairSync extends Sync { //.... } /** * 公平锁 */ static final class FairSync extends Sync { //.... } //默认构造函数,生成非公平锁 public ReentrantLock() { sync = new NonfairSync(); } /** * 根据实际情况,构造公平锁和非公平锁 */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } //.... }
2.1 公平锁
首先以公平锁FairSync为例
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; //争锁 final void lock() { acquire(1); } //代码来自父类AbstractQueuedSynchronizer, //如果tryAcquire(arg) 返回true, 也就结束了。 // 否则,acquireQueued方法会将线程压到队列中 public final void acquire(int arg) { //尝试获取suo if (!tryAcquire(arg) && // addWaiter 获取资源/锁失败后,将当前线程加入同步队列的尾部,并标记为独占模式,返回新的同步队列; // 使线程在同步队列等待获取资源,一直获取到后才返回,如果在等待过程中被中断过,则返回true,否则返回false。 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //没有获取到锁,放到同步队列后,这个时候需要把当前线程挂起, selfInterrupt(); } /** * 尝试直接获取锁,返回值是boolean * 返回true:1.没有线程在等待锁;2.重入锁,线程本来就持有锁,也就可以理所当然可以直接获取 */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // state == 0 此时此刻没有线程持有锁 if (c == 0) { //虽然此时此刻锁是可以用的,但是这是公平锁,要求先来先到 //hasQueuedPredecessors :当前线程是否还有前节点 //compareAndSetState 通过CAS操作修改AQS类中stateOffset地偏移量对应的state值 // 设置锁的持有者为当前线程。 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //如果当前锁状态state不为0,同时当前线程已经持有锁,由于锁是可重入(多次获取)的,则更新重入后的锁状态 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } //判断当前线程是否还有前节点 public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; //队列不为空,且头结点的下一个元素不是当前节点 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } //父类AbstractOwnableSynchronizer(AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer)中的方法 设置锁的持有者为当前线程。 protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; } //以尾差法的方式插入到同步队列中 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //pred==null(队列是空的) 或者 CAS失败(有线程在竞争入队) enq(node); return node; } //(队列是空的) 或者 CAS失败(有线程在竞争入队) //采用自旋的方式入队: CAS设置tail过程中,竞争一次竞争不到,我就多次竞争,总会排到的 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize //通过CAS操作,对双向链表的head和tail初始化 if (compareAndSetHead(new Node())) tail = head; //此时没有return,初始化链表的头尾节点,继续for循环,走到else分支 } else { //将当前线程排到队尾 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } //... }
在公平锁的lock中,当没有获取到锁,将当前线程加入到同步队列后,会调用acquireQueued方法。使线程在同步队列等待获取资源,一直获取到后才返回,如果在等待过程中被中断过,则返回true,否则返回false。
//将线程放入阻塞队列中 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); //获取前驱节点 //p == head 说明当前节点虽然进到了阻塞队列,但是是阻塞队列的第一个,因为它的前驱是head //所以当前节点可以去试抢一下锁(首先,它是队头,这个是第一个条件,其次,当前的head有可能是刚刚初始化的node(enq(node))) if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { // tryAcquire() 方法抛异常的情况 if (failed) cancelAcquire(node); } } //当前线程没有抢到锁,是否需要挂起当前线程 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; //前驱节点的 waitStatus == -1 ,说明前驱节点状态正常,当前线程需要挂起,直接可以返回true if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; // 前驱节点 waitStatus大于0 ,说明前驱节点取消了排队。 // 需要知道这点: 进入阻塞队列排队的线程会被挂起,而唤醒的操作是由前驱节点完成的。 // 将当前节点的prev指向waitStatus<=0的节点 if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 前驱节点的waitStatus不等于-1和1,那也就是只可能是0,-2(使用condition条件下),-3(针对共享锁) // 在我们前面的源码中,都没有看到有设置waitStatus的,所以每个新的node入队时,waitStatu都是0 // 正常情况下,前驱节点是之前的 tail,那么它的 waitStatus 应该是 0 // 用CAS将前驱节点的waitStatus设置为Node.SIGNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } // 这个方法返回 false,那么会再走一次 acquireQueued 中的for循序 // 此时会从第一个分支返回 true return false; } //将node节点作为头结点 private void setHead(Node node) { head = node; node.thread = null; node.prev = null; } //负责挂起线程,通过LockSupport.park(this)来挂起线程,然后就停在这里了,等待被唤醒 private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
最后,就是还介绍下唤醒的动作unlock。正常情况下,如果线程没获取到锁,线程会被 LockSupport.park(this); 挂起停止,等待被唤醒。
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; } protected final boolean tryRelease(int releases) { int c = getState() - releases; //判断当前线程是否持有锁 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); // 是否完全释放锁 boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } //唤醒后继节点,从调用处知道参数node是head头结点 private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; //如果head节点当前waitStatus<0, 将其修改为0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * 唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)所以需要从队尾往前找,找到waitStatus<=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); }
总结公平锁的流程:
2.2 非公平锁
非公平锁NonfairSync代码如下,通过对比公平锁和非公平锁tryAcquire的代码可以看到,非公平锁的获取略去了!hasQueuedPredecessors()这一操作,也就是说它不会判断当前线程是否还有前节点(prev node)在等待获取锁,而是直接去进行锁获取操作。
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 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. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了,若失败则调用tryAcquire;而公平锁则是直接调用tryAcquire方法。
2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态(是否处在队列的头部位置),如果有则不去抢锁,乖乖排到后面。如果非公平锁在两次 CAS操作后都不成功,那么它和公平锁是一样的,都要进入到阻塞队列等待唤醒。
3、在非公平锁下,当同步队列中有节点时,这时候来了新的更多的线程来抢锁,这些新线程还没有加入到同步队列中去,新线程会跟排队中苏醒的线程进行锁争抢,失败的去同步队列中排队。这里的公平与否,针对的是苏醒线程与还未加入同步队列的线程。对于已经在同步队列中阻塞的线程而言,它们内部自身其实是公平的,因为它们是按顺序被唤醒的,