Java并发编程AQS(AbstractQueuedSynchronizer)源码分析共享模式(二)

java并发编程AQS

之前分析了AQS独占模式的实现原理及可重入锁ReentrantLock基于AQS的实现,独占模式下始终只有一个线程能占用这把锁,当线程获取同部状态成功也就是CAS更新state成功的时候,会把当前线程设置为独占这个锁的线程。而竞争失败的线程将会构造成Node节点加入到AQS的同步队列尾部进行阻塞。可重入锁ReentrantLock在公平模式下每当线程进行锁释放,将会通知下一个节点进行锁的获取,一定程度上避免产出锁竞争,而锁的可重入性,在某些情况下一个线程会长期把持这把锁。而非公平模式获取同步状态时直接无条件进行CAS更新,而只有失败时才会将线程加入到同步队列中,非公平模式下多个线程同时获取锁会产生锁竞争,可能造成在队列中长时间阻塞的线程始终获取不到锁的情况。下面我来再来看AQS共享模式的实现部分以及基于共享模式下的ReentrantReadWriteLock读写锁。
共享模式很好理解,就是说一把锁可以由多个线程进行共享。

// 共享模式获取同步状态
public final void acquireShared(int arg) {
	// 当子类返回值小于0代表获取同步状态失败
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
// 提供给子类进行重写,与独占模式返回boolean类型不同的是,共享模式返回的int类型
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
// 获取同步状态失败,一直自旋不断尝试获取同步状态
private void doAcquireShared(int arg) {
	// 将当前线程构建成共享模式节点加入同步队列,addWaiter在上一篇分析过
    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) {
                	// 重新设置head指向当前节点,调用setHeadAndPropagate方法唤醒后面的节点
                    setHeadAndPropagate(node, r);
                    // 注意:获取成功后会移除下节点,后面讲解
                    p.next = null; 
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //  如果当前节点的上一个节点不是头节,则判断线程是否需要阻塞,与独占模式一模一样
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
        	//将线程对应node从同步队列中删除
            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;
        // 如果下节点不存在或者下节点是共享节点,调用doReleaseShared唤醒后续线程
        if (s == null || s.isShared())
            doReleaseShared();
    }
}
private void doReleaseShared() {
 	// 自旋
    for (;;) {
    	// 拿到头节点
        Node h = head;
        // 头节点存在,并且头节点与尾节点不是同一个,队列中还存在多个节点
        if (h != null && h != tail) {
        	// 拿到头节点状态
            int ws = h.waitStatus;
            // 头节点状态如果处于通知状态,则尝试更新成初始状态,失败同时跳过循环,再次自旋
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                // 唤醒下节点,独占模式讲过
                unparkSuccessor(h);
            }
            // 如果节点状态处于初始状态,设置为PROPAGATE用以保证setHeadAndPropagate方法唤醒线程的传递
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue; 
        }
        // 检查h是否仍然是head,如果不是的话需要再进行循环
        if (h == head)             
            break;
    }
}

共享模式获取同步:
共享锁获取同步跟独占锁差不多,只是获取同步失败的线程构建的是共享节点,而且节点加入到同步队列中会不断自旋来尝试获取锁,获取成功后会将后继所有共享节点唤醒,而独占模式只会唤醒一个,获取失败则继续自旋。
共享式释放同步

// 释放,也是调用模板方法releaseShared,返回ture再执行doReleaseShared进行释放。doReleaseShared方法上面讲过。
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

在读源码的时候,我们都能大概知道这些代码具体做了什么事情,但是为什么需要这样做?或者在多线程情况下这样做有什么好处?甚至在多线程竞争的情况下具体会产生哪些情况我们可能都不清楚,阅读源码也不可能在阅读几次的情况下就能完全了解这些设计思路,但是万事总要有开头,什么事情都是从不懂到略懂到熟悉…一直循环渐进,再说就算看懂源码,也可能远远达不到能再造一个能与之媲美的轮子的程度。(实际上AQS源码我看过不下5变,但是感觉与真正作者的思想境界相差千万里,惊叹为什么别人能写出这样逻辑,而自己看了这么多遍还只是理解皮毛)

ReentrantReadWriteLock可重入读写锁,通过维护一对锁,一个读锁一个写锁,在同一时刻允许多个读线程访问。但是在写线程访问时,所有的读线程和其他的写线程均被阻塞,适用于读多写少的并发情况,开始撸源码

// 可重入读写锁实现自读写锁接口
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable { ... }
        
// 读写锁接口       
public interface ReadWriteLock {
	// 返回一个读锁
    Lock readLock();
    // 返回一个写锁
    Lock writeLock();
}
// 私有的读写锁
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;

// Sync对象
final Sync sync;

// 默认构造函数,可重入读写锁默认是使用非公平模式
public ReentrantReadWriteLock() {
    this(false);
}
// 有参构造,可指定模式。
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}
// 外部返回写锁
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }

// 外部返回读锁
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

// Sync对象继承自AQS
abstract static class Sync extends AbstractQueuedSynchronizer{ ... }

// 继承自Sync的非公平模式类
static final class NonfairSync extends Sync { ... }

// 继承自Sync的公平模式类
static final class FairSync extends Sync { .. }

// 读锁和写锁都实现了Lock接口
public static class ReadLock implements Lock, java.io.Serializable { ... }
public static class WriteLock implements Lock, java.io.Serializable { ... }

// Unsafe 并发包中不可缺的一部分
private static final sun.misc.Unsafe UNSAFE;
private static final long TID_OFFSET;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        TID_OFFSET = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("tid"));
    } catch (Exception e) {
        throw new Error(e);
    }
}
// Sync类
abstract static class Sync extends AbstractQueuedSynchronizer {
 	// 共享读锁状态偏移位,偏移位为16
 	// 读锁与写锁公用父类同步状态state变量,state是个int变量,32位。读写锁使用按位切割,高16位表示读的状态,低16位表示写的状态。
	static final int SHARED_SHIFT   = 16;
	
	// 读锁操作的基本单元,读锁状态+1,则状态变量值+SHARED_UNIT
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
	
	//写锁可重入状态的最大值,也是读锁允许的最大数量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
	// 写锁掩码,将同步状态变量和掩码位 “与” 就可以得出写锁的状态
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    // 读锁计数,读锁的线程数
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 锁的计数,它的重入次数
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
    // 保存当前线程重入读锁的次数的容器,ThreadLocalHoldCounter继承ThreadLocal确保每个线程都有一个各自HoldCounter对象副本
    private transient ThreadLocalHoldCounter readHolds;
    // 重写initialValue返回持有计数器对象
    static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    // HoldCounter类,每个线程各自 read 计数。由上面得知放在ThreadLocal中
    static final class HoldCounter {
    	int count = 0;
    	// 使用常量id而不是引用是为了避免垃圾滞留。其实就是使用long记录线程id,而不是记录整个线程。一种优化手段
        final long tid = getThreadId(Thread.currentThread());
   	}
	// 最近一个成功获取读锁的线程的计数,缓存计数
    private transient HoldCounter cachedHoldCounter;
    // 为提升readHolds查找效率。当读计数等于0时,第一个获取读锁的线程不会放到readHolds里面去,而是单独用firstReader记录。
    private transient Thread firstReader = null;
    // firstReader线程的重入计数
    private transient int firstReaderHoldCount;
    
    // 构造器,内部实例了readHolds
   	Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }
    // 供子类实现的两个抽象方法。读与写是否需要阻塞
    abstract boolean readerShouldBlock();
    abstract boolean writerShouldBlock();
}

由抽象Sync类得知,子类必须实现两个抽象方法readerShouldBlock与writerShouldBlock。看看子类是怎么实现的。

// 非公平模式
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    // 写是否需要阻塞,直接返回false
    final boolean writerShouldBlock() {
        return false; /
    }
    // 写是否需要阻塞,由apparentlyFirstQueuedIsExclusive进一步判断
    final boolean readerShouldBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }
}
// 头节点的下节点就是在同步队列中等待的第一个节点,暂且称为目标节点
// isShared能够得出目标节点尝试获取的是读锁还是写锁,如果写锁,那么返回true。需要进行阻塞,读锁使用的是AQS中的共享模式,写锁使用的是AQS中独占模式
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    // 头节点存在,并且头节点的下一个节点也存在,并且头节点的下个节点不是共享节点,并且头节点的下一个节点线程不为null。同时满足返回true
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

//公平模式,读与写是否需要阻塞都是调用的同一个方法
static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}
// 公平模式只有一种情况是不需要排队,那就是线程重入。其他都需要排队 
public final boolean hasQueuedPredecessors() {
    // 拿到尾节点与头节点
    Node t = tail; 
    Node h = head;
    Node s;
    // 头尾不是同一个节点,并且头节点的下一个节点为null
    // 或者头节点的下一个节点线程不是当前线程
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

看看ReentrantReadWriteLock是怎么重写的AQS获取读锁的模板方法,Sync中的tryAcquireShared方法

//尝试 获取读锁
protected final int tryAcquireShared(int unused) {
	// 当前线程
    Thread current = Thread.currentThread();
    // 获取AQS state状态
    int c = getState();
    // exclusiveCount方法返回独占持有的计数等于写锁计数。如果当前有线程独占了这把锁,并且这个线程不是当前线程,获取读锁失败
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // sharedCount方法返回共享持有的计数,也就是读锁计数
    int r = sharedCount(c);
    // 如果读锁不需要阻塞且读锁数量未超过上限,并且获取同步状态成功
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
        	// 如果当前还没有其他线程获取读锁,那当前线程就是第一个获取读锁成功的线程。firstReader记录当前线程,并且重入计数设置为1
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
        	// 如果是重入,重入计数加1
            firstReaderHoldCount++;
        } else {
        	// 如果此时已经存在了其他获取了读锁的线程,拿到缓存计数
            HoldCounter rh = cachedHoldCounter;
            // 缓存计数为空,或者缓存的id不是当前线程id
            if (rh == null || rh.tid != getThreadId(current))
            	// 当当前线程计数对象赋给缓存计数对象
                cachedHoldCounter = rh = readHolds.get();
            // 如果之前缓存的是当前线程
            else if (rh.count == 0)
            	// 重新设置当前线程的读锁计数,为什么需要重新设置计数,因为上次在自旋获取同步状态中会被remove
                readHolds.set(rh);
            // 计数加1
            rh.count++;
        }
        return 1;
    }
    // 如果上面CAS获取读锁失败,会调用fullTryAcquireShared方法,保持自旋。
    return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    //自旋,不断获取读锁同步状态
    for (;;) {
    	// 拿到AQS同步状态state值
        int c = getState();
        // 如果独占计数存在,那就是说此时被写锁占用
        if (exclusiveCount(c) != 0) {
        	// 并且占用写锁的线程不是自己,直接返回失败
            if (getExclusiveOwnerThread() != current)
                return -1;
        } else if (readerShouldBlock()) {
            // 如果读需要阻塞
            // 这个判断是如果已经获取锁的线程是自己,那么为了避免死锁,需要进行重入计数
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                	// 拿到缓存计数
                    rh = cachedHoldCounter;
                    // 如果此时缓存计数为空或者缓存的id不是当前线程id,
                    if (rh == null || rh.tid != getThreadId(current)) {
                    	// 拿到当前线程副本中记录的读计数对象
                        rh = readHolds.get();
                        // 读计数等于0,销毁当前副本计数
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                // 不是重入,返回获取读锁同步状态失败
                if (rh.count == 0)
                    return -1;
            }
        }
        // 共享计数超出上限抛出异常
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 获取读锁同步状态成功,与tryAcquireShared处理一样
        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; 
            }
            return 1;
        }
    }
}

tryAcquireShared的逻辑:
1、尝试获取读锁,如果当前锁被写锁持有,且不是重入。获取失败
2、如果当前锁被写锁占用,但是这个写锁的获得者再此获取读锁将会成功,锁支持降级,写锁降级为读锁
3、如果发生在CAS获取读锁失败那一步,将会进入自旋不断尝试获取读锁,除非在这之间锁被其他写锁占用或者读锁即需要阻塞又不是重入
4、获取读锁失败的线程,会构建成共享节点,进入同步队列挂起等待唤醒。

读锁的释放

// ReentrantReadWriteLock读锁的释放,重写父类方法
protected final boolean tryReleaseShared(int unused) {
	// 拿到当前线程
    Thread current = Thread.currentThread();
    // 如果当前线程是第一个获取读锁成功的线程
    if (firstReader == current) {
    	// 计数只有1直接回收对象
        if (firstReaderHoldCount == 1)
            firstReader = null;
        // 否则,计数减1
        else
            firstReaderHoldCount--;
    } else {
    	// 如果当前线程不是第一个获取读锁成功的线程
    	// 拿到缓存计数
        HoldCounter rh = cachedHoldCounter;
        // 如果此时缓存计数为空或者缓存的id不是当前线程id,
        if (rh == null || rh.tid != getThreadId(current))
        	// 拿到当前线程副本中记录的读计数对象
            rh = readHolds.get();
        int count = rh.count;
        // 如果计数小于等于1次
        if (count <= 1) {
        	// 直接销毁副本计数
            readHolds.remove();
            // 如果计数值为0,抛出异常
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        // 计数值减1
        --rh.count;
    }
    //死循环
    for (;;) {
    	// 拿到AQS state变量值
        int c = getState();
        // 将值减1
        int nextc = c - SHARED_UNIT;
        // CAS更新成功后返回。
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

释放读锁:好像没什么好讲的。
ReentrantReadWriteLock写锁的获取

// 重写父类的独占模式获取同步状态方法
protected final boolean tryAcquire(int acquires) {
	// 拿到当前线程
    Thread current = Thread.currentThread();
    // 拿到AQS state变量值
    int c = getState();
    // 拿到写锁计数
    int w = exclusiveCount(c);
    // 如果state不等于0,当前锁已经被占用。
    if (c != 0) {
    	// 如果写锁计数为0或者当前线程不是占用锁的那个线程。返回失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 如果超出边界抛异常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 最后一种情况就只有重入了,重入状态加1.返回获取写锁同步状态成功
        setState(c + acquires);
        return true;
    }
    // 如果当前锁是空闲(未被任何读或写占用)
    // 如果写锁需要阻塞或者CAS更新state失败,返回同步状态获取失败
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 设置当前线程为占用锁的线程
    setExclusiveOwnerThread(current);
    return true;
}

tryAcquire的逻辑:
1、尝试获取写锁,如果锁被任何读锁或写锁占用,只要不是重入的情况都会失败
2、重入次数超出临界值,抛出异常
3、写锁如果判断为需要阻塞,获取CAS获取写锁失败,也会返回失败
4、写锁获取锁失败,会构建成独占节点,进入同步队列挂起等待唤醒。

写锁的释放

// 重写父类提供的模板方法,
protected final boolean tryRelease(int releases) {
	// 当前需要被释放的线程如果不是此刻占有锁的线程,抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // state减1
    int nextc = getState() - releases;
    // 独占计数如果等于0
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
    	// 清空独占线程标记
        setExclusiveOwnerThread(null);
    // 更新state
    setState(nextc);
    return free;
}

总结:ReentrantLock与ReentrantReadWriteLock中还有许多用于监控的方法未写出来,只是大概的看了一下。而且每次重新阅读源码都会有不同的收获,由于水平有限,不是短时间就可以理解的。之前两篇都只涉及到AQS中的同步队列,后续来看看其中等待队列是怎么实现的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值