AQS(AbstractQueuedSynchronizer)之——独占锁的实现

简介

AQS(AbstractQueuedSynchronizer)即队列同步器,它是J.U.C包下构建同步(锁)操作基础组件。其大体组成如下(以下描述基于JDK 1.8):

public abstract class AbstractQueuedSynchronizer {
    private transient volatile Node head; // 头节点,队列为null时有元素入队,head指向一个空node对象
    private transient volatile Node tail;	//记录最后一个节点
    
    private volatile int state; 
    public class ConditionObject implements Condition, java.io.Serializable {
		// 这里暂不做分析 
	}
    // 线程队列的节点, 队列的入队、出队逻辑在AQS类中实现
    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;	
        // 表示线程因为等待某个条件而被阻塞.(用于Condition的await等待)
        static final int CONDITION = -2; //
        // 表示锁的下一次获取可以无条件传播(共享锁中用到)
        static final int PROPAGATE = -3;
        volatile int waitStatus;	// 等待状态
        volatile Node prev;	//  前驱结点
        volatile Node next;	// 后继结点
        volatile Thread thread;
        Node nextWaiter;	// 等待状态的下一个节点
        
        Node() {    // Used to establish initial head or SHARED marker
        }
		// 入队时使用,mode匹配SHARED/EXCLUSIVE (共享/排他锁)
        Node(Thread thread, Node mode) { 
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
	}
}

调用过程

获取独占锁

1、acquire(int arg)方法获取锁

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 由子类实现,获取到同步则返回true
        	// 未获取到同步状态,先将当前节点加入阻塞队列
        	// 再一次尝试获取同步,若失败返回true, 阻塞当前线程
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

2、该如何获取锁由子类实现,如果获取到了锁则返回true

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

3、如果没有获取到锁,则会将当前节点加入到等待队列中

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 尝试一次入队,若失败,则转到enq方法多次尝试
        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;
            // 当头(尾)节点为null时,先使头尾节点指向一个空node对象
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

4、入队成功后,可能之前持有锁的线程可能已经释放锁了,所以需要 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))判断当前线程是否可以拿到锁了,不能则返回true,acquire方法中调用selfInterrupt()阻塞线程

  1. 如果当前节点的前驱结点为头节点:则再一次尝试获取锁,成功则线程执行;失败则阻塞
  2. 当前节点前驱结点不为头节点:查看前驱结点的状态,如果为signal,则返回true,阻塞线程;如果之前节点中的线程已取消获取锁的操作(waitStatus>0),移除队列中已取消的节点(或者前驱结点状态为waitStatus<0,则将其状态改为SIGNAL,表示线程完成/取消时通知后继结点),并返回false,acquireQueued方法中进入下一个循环,重新判断
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// 获取节点的前驱结点,判断是否头节点
                final Node p = node.predecessor();
                // 如果获取锁成功,则返回false,线程执行,不阻塞
                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;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
        	// 移除前驱结点中已取消的节点
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

释放独占锁

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 boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

2、如果锁可以释放,则需要唤醒队列中下一个线程

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        // 如果队列中后继结点已取消,则需要重新找到队列中最靠近当前节点的未取消的节点(waitStatus <= 0)
        // 如果后继结点为null,则下面这个if块什么都不做
        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);
    }

通过AQS简单实现可重入的独占锁

public class SynBuilder {
    public static void main(String[] args) {
        SynBuilder builder = new SynBuilder();
        Thread t1 = builder.newThread("线程1");
        Thread t2 = builder.newThread("线程2");
        t1.start();
        t2.start();
    }

    private final Syn syn = new NoFairSyn();
    public void lock() {
        syn.acquire(1);
        System.out.println(Thread.currentThread().getName() + " : 获得了锁");
    }
    public void unlock() {
        syn.release(1);
        System.out.println(Thread.currentThread().getName() + " : 获得了释放了锁");
    }
    private static class Syn extends AbstractQueuedSynchronizer {

    }
    private static class NoFairSyn extends Syn {
        @Override
        protected boolean tryAcquire(int acquires) {
            Thread ct = Thread.currentThread();
            int state = getState();
            if(state == 0) {    // 未加锁
                if(compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(ct);
                    return true;
                }
            // 判断锁是否当前线程持有
            } else if(getExclusiveOwnerThread() == ct) {
                int acquiresNum = acquires + getState();
                if(acquiresNum < 0) {
                    throw new IllegalArgumentException();
                }
                // 锁为当前线程持有,所以不用CAS
                setState(acquiresNum);
                return true;
            }
            return false;
        }
        @Override
        protected boolean tryRelease(int acquires) {
            if(getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            }
            int state = getState() - acquires;
            boolean free = false;
            if(state == 0) {
                setExclusiveOwnerThread(null);
                free = true;
            }
            setState(state);
            return free;
        }
    }

    public Thread newThread(String name) {
        return new Thread(() -> {
        	System.out.println(name + " 正在尝试获取锁");
            lock();
            lock();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                unlock();
                unlock();
            }
        }, name);
    }
}

输出为

线程1 正在尝试获取锁
线程2 正在尝试获取锁
线程1 : 获得了锁
线程1 : 获得了锁
线程1 : 获得了释放了锁
线程1 : 获得了释放了锁
线程2 : 获得了锁
线程2 : 获得了锁
线程2 : 获得了释放了锁
线程2 : 获得了释放了锁

参考

死磕Java并发

你可能感兴趣的:(java)