多线程--AQS原理

252 阅读6分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

定义:

全称AbstractQueuedSynchronizer,是阻塞式锁和相关同步器工具的框架。

特点

1、用state属性来表示资源的状态(独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

​ getState -- 获取state状态

​ setStatus -- 设置status状态

​ compareAndSetStatus -- cas机制设置state状态

​ 独占模式式只有一个线程能访问资源,而共享模式可以允许多个线程访问资源

2、提供了基于FIFO的等待队列,类似于Monitor 的EntryList

3、条件变量来实现等待、唤醒机制,支持多个条件变量,类似Monitor的WaitSet

子类主要实现以下方法(默认抛出UnsupportedOperationException)

tryAcquire

tryRelease

tryAcquireShared

tryReleaseShared

isHeldExclusively

自定义锁

/**
 * @Author blackcat
 * @version: 1.0
 * @description:自定义锁测试
 */
@Slf4j
public class MyLockTest {


    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
                Sleeper.sleep(1);
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t1").start();
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t2").start();
    }
}


/**
 * 自定义锁(不可重入锁)
 */
class MyLock  implements Lock {


    private  MySync  sync = new MySync();

    //独占锁
    class MySync extends AbstractQueuedSynchronizer{

        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0,1)){
                //加上锁,设置owner为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {

            //释放锁,设置owner为当前线程
            setExclusiveOwnerThread(null);
            //只有自己释放,不需要用cas设置
            //当前state是volatile,exclusiveOwnerThread 不是volatile
            //把这个放在后面添加写屏障使得上面的设置其他线程可见
            setState(0);
            return true;
        }

        //是否持有独占锁
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition(){
            return new ConditionObject();
        }
    }


    //加锁  不成功放入等待队列
    @Override
    public void lock() {
        sync.acquire(1);
    }

    //加锁可打断
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    //尝试加锁(尝试一次)
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    //尝试加锁(带超时时间)
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException{
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }

    //解锁,唤醒等待线程
    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

Reentrantlock

非公平锁实现原理

加锁解锁流程

  //构造器
  public ReentrantLock() {
        sync = new NonfairSync();
  }

 static final class  NonfairSync{
    final void lock() {
            // 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁 
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 如果尝试失败
                acquire(1);
        }
 }

没有竞争时候

image-20210306130949938.png

发生竞争时候

image-20210306131019353.png

   public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            // 当 tryAcquire 返回为 false 时, 先调用 addWaiter, 接着acquireQueued
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //获得锁以后重写打断
            selfInterrupt();
    }

整个这块代码里面包含了四个方法的调用,如下:

  1. tryAcquire,分别由继承 AQS 的公平锁(FairSync)、非公平锁(NonfairSync)实现。

    protected final boolean tryAcquire(int acquires) {
               return nonfairTryAcquire(acquires);
    }
    
    //Sync 继承过来的方法   
    final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                 //如果还没有获取锁
                if (c == 0) {
                    //尝试用cas获得
                    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;
            }
     
    
  2. addWaiter,该方法是 AQS 的私有方法,主要用途是方法 tryAcquire 返回 false 以后,也就是获取锁失败以后,把当前请求锁的线程添加到队列中,并返回 Node 节点。

     private Node addWaiter(Node mode) {
            //将当前线程关联一个Node 对象上,模式为独占模式
            Node node = new Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            //如果tail 不为null,cas 尝试将Node 对象加入AQS队列尾部
            Node pred = tail;
            if (pred != null) {
                //双向链表结构
                node.prev = pred;
                //compareAndSetTail 不一定一定成功,因为在并发场景下,可能会出现操作失败。那么失败               //后,则需要调用 enq 方法,该方法会自旋操作,把节点入队列。
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            //尝试将Node 加入AQS
            enq(node);
            return node;
        }
    
    //当队列为空时,则会新创建一个节点,把尾节点指向头节点,然后继续循环
    //第二次循环时,则会把当前线程的节点添加到队尾
     private Node enq(final Node node) {
         //自旋+CAS
            for (;;) {
                Node t = tail;
                if (t == null) { // Must initialize
                    //还没有,设置head为哨兵节点
                    if (compareAndSetHead(new Node()))
                        tail = head;
                } else {
                    // cas 尝试将Node 对象放入AQS队列尾部
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }
    
  3. acquireQueued,负责把 addWaiter 返回的 Node 节点添加到队列结尾,并会执行获取锁操作以及判断是否把当前线程挂起。

     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)) {
                        //如果获取成功,设置自己为head
                        //node 的prev和线程变成 null
                        setHead(node);
                        //上一个节点help GC
                        p.next = null; // help GC
                        failed = false;
                        //返回中断标记
                        return interrupted;
                    }
                    // 判断是否应当 park
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        // park 等待, 此时 Node 的状态被置为 Node.SIGNAL
                        parkAndCheckInterrupt())
                        //如果是因为interrupt 被唤醒,返回打断标记为true
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    
     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            // 获取上一个节点的状态
            int ws = pred.waitStatus;
            // 上一个节点都在阻塞, 那么自己也阻塞好了
            if (ws == Node.SIGNAL)
                /*
                 * This node has already set status asking a release
                 * to signal it, so it can safely park.
                 */
                return true;
             // > 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 must be 0 or PROPAGATE.  Indicate that we
                 * need a signal, but don't park yet.  Caller will need to
                 * retry to make sure it cannot acquire before parking.
                 */
                // 这次还没有阻塞
              // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }
    
    
    
    // 线程挂起等待被唤醒    
    private final boolean parkAndCheckInterrupt() {
        //如果打断标记已经是true,则park失效
        LockSupport.park(this);
        //调用interrupted 清除打断标记
        return Thread.interrupted();
    }  
    
  4. selfInterrupt,是 AQS 中的 Thread.currentThread().interrupt() 方法调用,它的主要作用是在执行完 acquire 之前自己执行中断操作。

Thread-1 执行了

​ 1.CAS 尝试将state 由0改为1,结果失败

​ 2.进入tryAcquire逻辑,这时stste已是1,结果仍然失败

​ 3.接下来进入addWaiter的逻辑,构造Node队列

​ 图中黄色三角表示Node的waitStatus状态,其中0为默认正常状态

​ Node的创建时懒惰的

​ 其中第一个Node时哑元(或哨兵),用来占位,并不用来关联线程

image-20210306131747135.png

当前线程进入acquireQueued逻辑

1.acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞

2.如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁(如果获取成功,设置自己为head)

3.如果再次获取锁失败, 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

4.shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued

5.当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true

6.进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

image-20210306160148283.png

再次有多个线程经历上述过程竞争失败,变成这个样子

image-20210306155537746.png

当thread-0 释放解锁后,会唤醒下一个thread-1的节点

image-20210306160831765.png

如果加锁成功(没有竞争),会设置

exclusiveOwnerThread 为 Thread-1,state = 1

head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread

原本的 head 因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

image-20210306155537746.png

如果不巧又被 Thread-4 占了先

Thread-4 被设置为 exclusiveOwnerThread,state = 1

Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

解锁源码

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
 // 解锁实现
  public void unlock() {
     sync.release(1);
  }
}

//从AQS继承
public final boolean release(int arg) {
        //尝试释放锁 
        if (tryRelease(arg)) {
            //队列头节点
            Node h = head;
            //队列不为null,状态为Node.SIGNAL
            if (h != null && h.waitStatus != 0)
                //unpark AQS中的等待线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

//Sync 继承
protected final boolean tryRelease(int releases) {
            //state --  
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
    // 支持锁重入, 只有 state 减为 0, 才释放成功
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
}

//AQS 继承
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.
         */
        // 如果状态为 Node.SIGNAL 尝试重置状态为 0
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
      // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
        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);
    }

公平锁实现原理

static final class FairSync extends Sync {
       final void lock() {
           acquire(1);
       }
     // AQS 继承过来的方法
      public final void acquire(int arg) {
         if (!tryAcquire(arg) && 
              acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
              selfInterrupt();
            }
       }    
    
       // 与非公平锁主要区别在于 tryAcquire 方法的实现
 		protected final boolean tryAcquire(int acquires) {
			 final Thread current = Thread.currentThread();
 			 int c = getState();
 			 if (c == 0) {
 				 // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
 				if (!hasQueuedPredecessors() &&
 					compareAndSetState(0, acquires)) {
					 setExclusiveOwnerThread(current);
 				 return true;
 				}
 			}
 			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;
          // h != t 时表示队列中有 Node
        return h != t &&
            // (s = h.next) == null 表示队列中还有没有老二
            // 或者队列中老二线程不是此线程
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }  

条件变量的原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

  //AQS 
 public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //添加一个 Node 至等待队列
            Node node = addConditionWaiter();
            //释放同步器上面的锁,唤醒下一个节点
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                //阻塞当前线程
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
  }

public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }



private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
                //将该Node 加入AQS 尾部
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
}