显示锁:AQS

 谈到并发,不得不谈ReentrantLock;而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!

/**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

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

   AQS的成员变量不多

     1. state(代表共享资源)

     2.head, tail 来变量来维护一个FIFO线程等待队列 简称CLH(多线程争用资源被阻塞时会进入此队列)

CLH队列

static final class Node {
    int waitStatus;
    Node prev;
    Node next;
    Node nextWaiter;
    Thread thread;
}

waitStatus:表示节点的状态,其中包含的状态有:
     CANCELLED:值为1,表示当前节点被取消;
     SIGNAL:值为-1,表示当前节点的的后继节点将要或者已经被阻塞,在当前节点释放的时候需要unpark后继节点;
     CONDITION:值为-2,表示当前节点在等待condition,即在condition队列中;
     PROPAGATE:值为-3,表示releaseShared需要被传播给后续节点(仅在共享模式下使用);
     0:无状态,表示当前节点在队列中等待获取锁。
prev:前继节点;
next:后继节点;
nextWaiter:存储condition队列中的后继节点;
thread:当前线程。

AQS定义两种资源共享方式:。

1.当模式为Exclusive(独占,只有一个线程能执行,如ReentrantLock),程序会从CLH队列一个一个唤醒.

    ReentrantLock.lock方法,调用AQS的acquire(1)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//尝试获取独占锁;
protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 获取state
        int c = getState();
        // state=0表示当前队列中没有线程被加锁
        if (c == 0) {
            /*
             * 首先判断是否有前继结点,如果没有则当前队列中还没有其他线程;
             * 设置状态为acquires,即lock方法中写死的1(CAS来执行);
             * 设置当前线程独占锁。
             */
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        /*
         * 如果state不为0,表示已经有线程独占锁了,这时还需要判断独占锁的线程是否是当前的线程,原因是由于ReentrantLock为可重入锁;
         * 如果独占锁的线程是当前线程,则将状态加1,并setState;
         * 这里为什么不用compareAndSetState?因为独占锁的线程已经是当前线程,不需要通过CAS来设置。
         */
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

//该方法就是根据当前线程创建一个Node,然后添加到队列尾部
private Node addWaiter(Node mode) {
    // 根据当前线程创建一个Node对象
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // 判断tail是否为空,如果为空表示队列是空的,直接enq
    if (pred != null) {
        node.prev = pred;
        // 这里尝试CAS来设置队尾,如果成功则将当前节点设置为tail,否则enq
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return 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为当前节点(head中不包含thread)
                setHead(node);
                // 清除之前的head
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果p不是head或者获取锁失败,判断是否需要进行park
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            //如果在循环的过程中出现了异常,则执行cancelAcquire方法,用于将该节点标记为取消状态。
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL) //只有在前继节点的状态是SIGNAL时,需要park
        return true;
    if (ws > 0) {
        //如果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();
}

该方法主要工作如下:

  1. 尝试获取独占锁;
  2. 获取成功则返回,否则执行步骤3;
  3. addWaiter方法将当前线程封装成Node对象,并添加到队列尾部;
  4. 自旋获取锁,并判断中断标志位。如果中断标志位为true,执行步骤5,否则返回;
  5. 设置线程中断。

    ReentrantLock.unlock方法,调用AQS的release(1)

public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) {
        // 释放成功后unpark后继节点的线程
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //当前线程被释放之后,需要唤醒下一个节点的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    // 这里是将锁的数量减1
    int c = getState() - releases;
    // 如果释放的线程和获取锁的线程不是同一个,抛出非法监视器状态异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 由于重入的关系,不是每次释放锁c都等于0,
    // 直到最后一次释放锁时,才会把当前线程释放
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 记录锁的数量
    setState(c);
    return free;
}

private void unparkSuccessor(Node node) {
   
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //从队列尾部向前遍历找到最前面的一个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);//唤醒
}

该方法主要工作如下:

  1. 尝试释放锁;
  2. 获取没完全释放(重入)则返回,否则执行步骤3;
  3. 释放成功后unpark后继节点的线程

2.当模式为Share(共享,多个线程可同时执行,如ReadWriteLock.readLock),程序会从CLH队列唤醒一个,给予资源,如果下一个也是共享型,则继续唤醒下一个,如果不是则等待资源。

ReadWriteLock的作用是,写的时候只能一个线程拥有锁,读的时候可以多个线程同时拥有。基中先看一下其state的设计

可以看到state的高16位代表读锁的个数;低16位代表写锁的状态。

//读锁获取共享锁直接用的是AQS中的
public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
}

//读锁尝试获取共享锁
//exclusiveCount获取state低8位的数值,也就是写线程重入数。
//sharedCount获取state高8位数值,就是共享线程数
//用HoldCounter,其实是ThreadLocal保存共享线程的重入数,首个线程直接保存在firstReader ,firstReaderHoldCount。
protected final int tryAcquireShared(int unused) {
           
            Thread current = Thread.currentThread();
            int c = getState();
            //如果当前有写线程并且本线程不是写线程,不符合重入,失败
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //得到读锁的个数
            int r = sharedCount(c);
            //如果读不应该阻塞并且读锁的个数小于最大值65535,并且可以成功更新状态值,成功
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {//第一个读线程就是当前线程
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {//重入线程
                    firstReaderHoldCount++;//重入数加1
                } else {//当前读线程和第一个读线程不同,记录每一个线程读的次数
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            //CAS循环尝试
            return fullTryAcquireShared(current);
        }


final int fullTryAcquireShared(Thread current) {
           
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {//一旦有别的线程获得了写锁,返回-1,失败
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                } else if (readerShouldBlock()) {//如果读线程需要阻塞(如果是公平锁,则要排队)
                    if (firstReader == current) {//发现是自己占了锁,什么都不做
                        // assert firstReaderHoldCount > 0;
                    } else {//别的线程占用了,则返回-1表示获取锁失败。
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //如果成功更改状态,成功返回
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }


//AQS中享锁的处理方式
  private void doAcquireShared(int arg) {
        //这是加入CLH的是Node.SHARED模式节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {//获取成功则更新CLH的头节点
                        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);
        }
    }

  //独立模式和共享模式的主要区分点
  private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        setHead(node);
        //如果获取成功,且下个节点也是共享模式,则继续唤醒下个结点
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

Condition的设计

Condition 是对java.lang.Object上监视器方法,包括wait()wait(long timeout)notify()notifyAll()一种代替。可以更加易于理解。

public class ConditionObject implements Condition, java.io.Serializable {
        private transient Node firstWaiter;
        private transient Node lastWaiter;
  
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
        
       //获取锁后的等待。。
       public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            ....
       }

       //当条件发生后的唤醒
       private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
        
       final boolean transferForSignal(Node node) {
        
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //把节点加到CLH队列的后面
        Node p = enq(node);
        int ws = p.waitStatus;
        //改变节点状态,试着唤醒一波
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
}

从实现类可以看出来了,唤醒过程如下

睡觉过程如下

总结:

AQS总的架构图

节点分为共享和独享2种模式,另外state的可以让继承类自由设计,Watiter队列和条件是一对一的。


 AQS的成员变量不多

  1. state(代表共享资源)
  2. head, tail 来变量来维护一个FIFO线程等待队列 简称CLH(多线程争用资源被阻塞时会进入此队列)
  3. hread(当前执行的线程)

独占模式Exclusive,以ReentrantLock为例

  1.  state 为 0 ,thread = null
  2.  线程A请求资源,state以cas方式设置为1,thread设置为线程A
  3.   如果线程A再次请求资源(重入),state + 1
  4.   线程B请求资源,加入到CLH队列中阻塞,等待唤醒
  5.   线程A释放资源state=0,唤醒CLH队列中head结点,去用compareAndSwap去进行锁的占用
  6.   此时线程C请求资源,资源state=0,如果是NonfairSync非公平锁状态,不判断是否有等待队列,直接使用compareAndSwap去进行锁的占用
  7.   此时线程C请求资源,资源state=0,如果是fairSync公平锁状态,判断是否有等待队列,如果有则将自己加到等待队列尾

共享模式Share,以ReadWriteLock为例

  1.  state的高16位代表读锁的个数;低16位代表写锁的状态
  2.  线程A请求写资源,低16位state = 1
  3.  线程B请求读资源,加入到CLH队列中阻塞标记为Share
  4.  线程C请求读资源,加入到CLH队列中阻塞标记为Share
  5.  线程D请求写资源,加入到CLH队列中阻塞标记为Exclusive
  6.  线程A释放写资源,低16位state = 0,唤醒CLH队列中head(B)结点,发现head结点为Share,继续唤醒head(C),最终停于head(D).
  7.  线程B,C获取读资源,高16位state=2

Condition的设计
Condition 是对java.lang.Object上监视器方法,包括wait(),wait(long timeout),notify(),notifyAll()一种代替。

  1. 每以锁创建一个Condition,则会自动生成一个waiter队列
  2. 当以Condition的wait时候,生成节点加入waiter队列
  3. 当以Condition的notify时候,waiter队列关结点,加入到CLH队列中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值