• concurrent-锁


    AQS
    ReentrantLock
    ReentrantReadWriteLock

    AQS

    全名叫AbstractQueuedSynchronizer。
    image

    public abstract class AbstractQueuedSynchronizer
            extends AbstractOwnableSynchronizer
            implements java.io.Serializable {
        //链表队列头和尾
        private transient volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node head;
        private transient volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node tail;
        
        //核心属性,竞争的资源指的就是抢占这个属性
        private volatile int state;
    
       //当前持有state的线程
        private transient Thread exclusiveOwnerThread;
    
        static final class Node {//每个node代表一个线程
            //标记线程是因为获取共享资源失败而进入队列的
            static final java.util.concurrent.locks.AbstractQueuedSynchronizer.Node SHARED = new java.util.concurrent.locks.AbstractQueuedSynchronizer.Node();
            //标记线程是因为获取独占资源失败而进入队列的
            static final java.util.concurrent.locks.AbstractQueuedSynchronizer.Node EXCLUSIVE = null;
    
            //表示线程因为中断或者等待超时,需要从等待队列中取消等待
            //该状态node会自动被jvm回收
            static final int CANCELLED = 1;
            //已占有锁的线程释放时会通知该状态节点去抢占资源
            static final int SIGNAL = -1;
            //标识该节点在condition队列中。
            // 备注:lock的每个condition都有一个阻塞队列,当调用了signal方法后,队列中的节点会移动到clh队列中
            //类似sychronize的waitSet
            static final int CONDITION = -2;
            static final int PROPAGATE = -3;
    
            volatile int waitStatus;
    
            //队列是双向链表
            volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node prev;
            volatile java.util.concurrent.locks.AbstractQueuedSynchronizer.Node next;
    
            volatile Thread thread;
    
            java.util.concurrent.locks.AbstractQueuedSynchronizer.Node nextWaiter;
    
        }
    
        //用来存放condition阻塞队列
        public class ConditionObject implements Condition, java.io.Serializable {
            private static final long serialVersionUID = 1173984872572414699L;
            /**
             * First node of condition queue.
             */
            private transient AbstractQueuedSynchronizer.Node firstWaiter;
            /**
             * Last node of condition queue.
             */
            private transient AbstractQueuedSynchronizer.Node lastWaiter;
        
        }
    
    }
    

    返回顶部

    ReentrantLock

     public static void main(String[] args) {
    
            ReentrantLock lock = new ReentrantLock();//构造器
    
            lock.lock();//加锁
    
            lock.unlock();//解锁
    
        }
    

    构造器:

    public class ReentrantLock implements Lock, java.io.Serializable {
        //两个实现类:NonfairSync-非公平锁,FairSync-公平锁
      //NonfairSync和FairSync都是ReentrantLock内部类
        private final java.util.concurrent.locks.ReentrantLock.Sync sync;
        public ReentrantLock(boolean fair) {
            sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
        }
        public ReentrantLock() {//默认是非公平锁
            sync = new ReentrantLock.NonfairSync();
        }
    }
    

    我们先分析非公平锁。

    这里先总结一下lock方法的全部流程:
    1、首先通过cas尝试获取state,如果成功,直接更新state持有线程为当前线程
    2、如果失败,先判断一下state是否为0,如果为0,再次尝试通过cas获取state,成功更新state持有线程
    3、如果失败,则判断一下state当前持有线程是否为当前线程,如果是,则可以重入,直接更新重入次数+1,结束流程
    4、如果失败,也不可以重入,那么将当前线程封装到node中,然后通过尾插法插入到队列
    插入队列逻辑:
    1、首先判断当前队列尾节点是否为null,如果不为null,则通过cas插入尾部
    2、如果当前队列尾节点为null,则初始化一个队列,然后通过cas插入尾部。
    如果当前队列尾节点不为null,但是在第一步cas插入失败,则通过cas方式循环不停向尾部插入。
    总之这一步完成后节点一定是插入到队列尾部了。
    接下来的逻辑是在一个无限循环内部:
    1、首先判断一下当前节点的前驱节点是否为队列头部,如果是头部,则该节点再次尝试获取state,如果成功,则将当前节点更新为队列头部。
    2、如果不是头部,或者获取state失败,则当前节点通过LockSupport.park(this);方法阻塞自己。
    注意:这是在一个循环里阻塞的,当被唤醒时,循环会继续。

    加锁:
    ReentrantLock.NonfairSync.lock():首先cas方式尝试更新state,成功更新state持有者为当前线程,失败进入acquire方法。

    //    ReentrantLock.NonfairSync
        final void lock() {
            if (compareAndSetState(0, 1))//cas方式更新state值为1
                setExclusiveOwnerThread(Thread.currentThread());//入口
            else
                acquire(1);//入口
        }
    
    //    AbstractOwnableSynchronizer
        protected final void setExclusiveOwnerThread(Thread thread) {
            exclusiveOwnerThread = thread;
        }
    

    AbstractOwnableSynchronizer.acquire:

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

    接下来分别看上面的四个方法:tryAcquire、acquireQueued、addWaiter、selfInterrupt。

    tryAcquire:再次尝试直接获取state,如果失败在判断是否是锁重入

    //    ReentrantLock.NonfairSync
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
        //    ReentrantLock.NonfairSync
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//如果state没有线程占用
                if (compareAndSetState(0, acquires)) {//尝试获state
                    setExclusiveOwnerThread(current);//获取state成功,更新锁持有线程为当前线程
                    return true;
                }
            }
            //如果state已经有线程占用了,但是占用的线程为当前线程,就是锁重入
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;//更新锁重入次数 +1
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;//其他情况返回false
        }
    

    addWaiter:创建一个node,添加到aqs队列尾部,如果失败(多线程竞争)通过循环cas方式知道成功为止。

     //    AbstractOwnableSynchronizer
        private Node addWaiter(Node mode) {
            Node node = new Node(Thread.currentThread(), mode);
    
            Node pred = tail;//尾插法
            if (pred != null) {//如果队列原尾节点不为null,将当前节点插入到队列尾部
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            //代码如果走到这里有两种情况,1:队列还未初始化即队列原尾节点为null。
            // 2:刚才向队列尾部插入节点失败,有其他线程竞争插入。即compareAndSetTail(pred, node)失败
            enq(node);//入口
            return node;
        }
        private AbstractQueuedSynchronizer.Node enq(final AbstractQueuedSynchronizer.Node node) {
            //该方法是往队列尾部插入节点
            // 当队列还未初始化时,会初始化一个队列,然后插入到尾部
            //当队列已经初始化,则通过cas方式,失败不停重试,向尾部插入节点
            for (;;) {//cas方式  失败不停循环
                AbstractQueuedSynchronizer.Node t = tail;
                if (t == null) { // 如果队列原尾节点为null,
                    if (compareAndSetHead(new AbstractQueuedSynchronizer.Node()))
                        tail = head;
                } else {
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }
    

    AbstractOwnableSynchronizer.acquireQueued:参数是刚才添加到队列中的node,该方法判断node的前驱节点是否是链表head,如果是,尝试获取state,如果成功,则更新node为链表head。
    如果失败,则阻塞住自己,这是在一个循环中阻塞。等到state持有线程释放锁会唤醒阻塞线程,循环继续。

     //    AbstractOwnableSynchronizer
        final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final AbstractQueuedSynchronizer.Node p = node.predecessor();//获取前驱节点
                    if (p == head && tryAcquire(arg)) {//如果前驱节点是头节点,则尝试获取state
                        //如果获取state成功了,则将当前node(state持有线程的node)设置为链表的head
                        setHead(node);
                        p.next = null;
                        failed = false;
                        return interrupted;
                    }
                    //如果不是头节点或者获取state失败,则阻塞自己。
                    //注意:这是在一个循环里阻塞的,当被唤醒时,循环会继续
                    if (shouldParkAfterFailedAcquire(p, node) &&
                            parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    

    锁释放

    ReentrantLock.unlock:
    1、首先将state值减去1,如果state大于0,证明锁重入,直接结束。如果state等于0,还要更新state当前持有线程为null
    2、从头节点开始往下找,如果节点状态不为-1,则更新其状态为作废(jvm自动回收),直到找到第一个状态为-1的节点,然后调用唤醒。
    这样就和上面在循环中阻塞连接起来了。

      //    ReentrantLock
    public void unlock() {
        sync.release(1);
    }
    //AbstractOwnableSynchronizer
        public final boolean release(int arg) {
            if (tryRelease(arg)) {
                AbstractQueuedSynchronizer.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) {//锁的进入次数为0
                free = true;
                setExclusiveOwnerThread(null);//更新state持有线程未null
            }
            setState(c);//如果锁的重入次数未降到0,则直接更新state次数-1就结束
            return free;
        }
    
     private void unparkSuccessor(Node node) {
            int ws = node.waitStatus;
            if (ws < 0)//如果节点的状态小于0,则通过cas更新为0
                compareAndSetWaitStatus(node, ws, 0);
    
            Node s = node.next;//获取节点的next节点
            if (s == null || s.waitStatus > 0) {//如果next节点为null或者状态不为-1,则将next节点更新为null
                s = null;
                for (Node t = tail; t != null && t != node; t = t.prev)//将最近的状态为-1的节点设置给s
                    if (t.waitStatus <= 0)
                        s = t;
            }
            if (s != null)
                LockSupport.unpark(s.thread);//唤醒
        }
    

    上面是非公平锁的分析,公平锁又是什么样的?
    非公平锁是先获取锁,失败进入队列。
    公平锁先进入队列,排队获取锁。

    返回顶部

    ReentrantReadWriteLock

      public static void main(String[] args) {
            ReadWriteLock lock = new ReentrantReadWriteLock();
            Lock readLock = lock.readLock();
            Lock writeLock = lock.writeLock();
    
            //读和读不互斥
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        readLock.lock();
                        Thread.sleep(3000);
                        readLock.unlock();
                        System.out.println("读锁释放");
                    }catch (Exception e){
                    }
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        Thread.sleep(1000);
                        readLock.lock();
                        System.out.println("获取到读锁");
                        readLock.unlock();
                    }catch (Exception e){
                    }
                }
            }).start();
        }
    
    public class ReentrantReadWriteLock
            implements ReadWriteLock, java.io.Serializable {
        private final java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock readerLock;
        private final java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock writerLock;
        final java.util.concurrent.locks.ReentrantReadWriteLock.Sync sync;
    }
    
      //重入计数器,读锁 -- 可重入锁  ,  该属性用来记录每个线程重入读锁次数
        static final class HoldCounter {
            int count = 0;
            final long tid = getThreadId(Thread.currentThread());
        }
    
        //HoldCounter是用来记录一个线程的次数    readHolds存储所有线程的HoldCounter
        private transient ThreadLocalHoldCounter readHolds;
        static final class ThreadLocalHoldCounter//一个内部类的定义
                extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }
        /** 最近一个成功获取读锁的线程的计数。这省却了ThreadLocal查找 缓存*/
        private transient HoldCounter cachedHoldCounter;
    

    state是32位int类型的,ReentrantReadWriteLock将其拆分成两段,高16位用来表示读锁数量,低16位用来表示写锁数量。

    分别分析四个方法:readLock.lock readLock.unlock writeLock.lock writeLock.unlock

    readLock.lock

    先总结流程:
    1、首先获取state写锁段位,如果大于0,证明有写锁,进入插入队列逻辑(同ReentrantLock一样)
    2、如果state写锁段位小于0,则通过循环cas方式更新读锁直到成功。
    有几点需要注意:
    1、第一个读锁单独缓存到firstReader,性能提升
    2、通过ThreadLocal和HoldCounter类来存储每个线程锁state的值

     //    ReentrantReadWriteLock
        public void lock() {
            sync.acquireShared(1);
        }
    //AbstractQueuedSynchronizer
        public final void acquireShared(int arg) {
            if (tryAcquireShared(arg) < 0)
                doAcquireShared(arg);
        }
    
    //    ReentrantReadWriteLock
    protected final int tryAcquireShared(int unused) {
        Thread current = Thread.currentThread();
        int c = getState();
        //state写锁位段大于0,并且当前线程不是state持有线程,直接返回-1
        if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
            return -1;
        int r = sharedCount(c);//获取state写锁位段
        if (!readerShouldBlock() &&
                r < MAX_COUNT &&//
                compareAndSetState(c, c + SHARED_UNIT)) {
            //如果读锁数量未达到上线,并且cas获取state读锁成功
            if (r == 0) {
                //state的第一个读锁单独用firstReader来存储,增加性能
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                //当前线程就是firstReader
                firstReaderHoldCount++;
            } else {//通过ThreadLocal类型的HoldCounter来存储锁的次数
                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);
    }
        //    ReentrantReadWriteLock
        final int fullTryAcquireShared(Thread current) {
            ReentrantReadWriteLock.Sync.HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                } else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        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;
                }
            }
        }
    

    AbstractQueuedSynchronizer.doAcquireShared:

    //AbstractQueuedSynchronizer
    private void doAcquireShared(int arg) {
        //首先添加节点到队列尾部,逻辑同ReentrantLock
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {//如果前驱节点是头节点,则尝试获取state
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                //如果不是头节点或者获取state失败,则阻塞自己。
                //注意:这是在一个循环里阻塞的,当被唤醒时,循环会继续
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

    释放读锁

    //ReentrantReadWriteLock
        public void unlock() {
            sync.releaseShared(1);
        }
    //    AbstractQueuedSynchronizer
        public final boolean releaseShared(int arg) {
            if (tryReleaseShared(arg)) {
                doReleaseShared();
                return true;
            }
            return false;
        }
    

    分别调用了两个方法:tryReleaseShared和doReleaseShared

    ReentrantReadWriteLock.tryReleaseShared:更新holdCounter和state

    //    ReentrantReadWriteLock
        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                //如果当前线程是firstReader,firstReaderHoldCount减一,更新firstReader
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {//更新ThreadLocalHoldCounter
                ReentrantReadWriteLock.Sync.HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {//循环cas更新state
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    

    AbstractQueuedSynchronizer.doReleaseShared:队列节点处理

    //    AbstractQueuedSynchronizer
        private void doReleaseShared() {
            for (;;) {
                AbstractQueuedSynchronizer.Node h = head;
                if (h != null && h != tail) {
                    int ws = h.waitStatus;
                    if (ws == AbstractQueuedSynchronizer.Node.SIGNAL) {
                        if (!compareAndSetWaitStatus(h, AbstractQueuedSynchronizer.Node.SIGNAL, 0))
                            continue;            // loop to recheck cases
                        unparkSuccessor(h);
                    }
                    else if (ws == 0 &&
                            !compareAndSetWaitStatus(h, 0, AbstractQueuedSynchronizer.Node.PROPAGATE))
                        continue;                // loop on failed CAS
                }
                if (h == head)                   // loop if head changed
                    break;
            }
        }
    

    释放比较简单:更新HoldCounter,更新队列node。

    写锁的上锁和释放锁和ReentrantLock是一样的。

    返回顶部

  • 相关阅读:
    {转}每次从vss获取文件都是只读
    點擊按鈕后彈出新頁面導致原頁面CSS失效
    Mschat控件示例升级错误处理方法
    一个简单的使用EVP框架的加密过程 aes 128 ecb
    centos7的syslog知识点
    pam模块使用syslog日志调试
    Linux系统上的popen()库函数
    linux C语言 用openssl进行签名验签 --- 亲测2 sha256 sha512
    linux C语言 用openssl进行签名验签 --- 亲测 sha256 sha512
    k8s简介
  • 原文地址:https://www.cnblogs.com/yanhui007/p/12587270.html
Copyright © 2020-2023  润新知