On the core Java Concurrency (b)

review

In the previous Java Concurrency On the core we probably learned Lockand synchronizedcommon ground, and then briefly summarize below:

  • LockA main counter is the custom, to take advantage of CAStheir atomicity operation, and synchronizedis c++ hotspotrealized monitor (we did not see the specific, we can not say)
  • Both can be reentrant (recursively, intermodulation, cycles), which essentially maintains a count of the counter, when the other threads from accessing the lock object determines whether the counter is 0
  • Both are blocking the theory, because the thread when to take the lock, if not get the end result can only wait (provided that the ultimate goal is to get a thread lock) into two separate read-write lock lock , it is not the same

For example: A thread holds the monitor of an object, other threads when accessing the object and found that monitor is not 0, it can only be blocked pending or join the waiting queue, waiting for thread A monitor will exit processed set to zero. A thread during the processing task, other threads or iterate monitor, or has been blocked waiting for thread A wake-up, then bad really as I said, to give up lock competition, to deal with other tasks. After it should be impossible to deal with other tasks, task processing to half, and then go back to be notified thread A grab lock

Fair locking and non-locking fair

Do not share counter

        // 非公平锁在第一次拿锁失败也会调用该方法
        public final void acquire(int arg) {
        // 没拿到锁就加入队列
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
        }
        
        // 非公平锁方法
        final void lock() {
            // 走来就尝试获取锁
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1); // 上面那个方法
        }
        
        // 公平锁 Acquire 计数
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            // 拿到计数
            int c = getState();
            if (c == 0) {
                // 公平锁会先尝试排队 非公平锁少个 !hasQueuedPredecessors() 其它代码一样
                if (!hasQueuedPredecessors() &&
                    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;
        }
        
        /**
         * @return {@code true} if there is a queued thread preceding the // 当前线程前有线程等待,则排队
         *         current thread, and {@code false} if the current thread
         *         is at the head of the queue or the queue is empty // 队列为空不用排队
         * @since 1.7
         */
        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;
            // 当前线程处于头节点的下一个且不为空则不用排队
            // 或该线程就是当前持有锁的线程,即重入锁,也不用排队
            return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
        }
        
        // 加入等待队列
        final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 获取失败会检查节点状态
                // 然后 park 线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    
        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1; // 线程取消加锁
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;  // 解除线程 park
        /** waitStatus value to indicate thread is waiting on condition */ // 
        static final int CONDITION = -2; // 线程被阻塞
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3; // 广播
        
        // 官方注释
        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;
复制代码

Read lock and write lock (shared lock and the exclusive lock)

Read lock: shared counter

Write locks: do not share counter

        // 读写锁和线程池的类似之处
        // 高 16 位为读计数,低 16 位为写计数
        static final int SHARED_SHIFT   = 16;
        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;

        /** Returns the number of shared holds represented in count. */ // 获取读计数
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Returns the number of exclusive holds represented in count. */ // 获取写计数
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
        
        /**
         * A counter for per-thread read hold counts. 每个线程自己的读计数
         * Maintained as a ThreadLocal; cached in cachedHoldCounter.
         */
        static final class HoldCounter {
            int count;          // initially 0
            // Use id, not reference, to avoid garbage retention
            final long tid = LockSupport.getThreadId(Thread.currentThread()); // 线程 id
        }
        
    // 尝试获取读锁
    protected final int tryAcquireShared(int unused) {
            // ReentrantReadWriteLock ReadLock 读锁
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            // 如果写锁计数不为零,且当前线程不是写锁持有线程,则可以获得读锁
            // 言外之意,获得写锁的线程不可以再获得读锁
            // 个人认为不用判断写计数也行
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // 获得读计数
            int r = sharedCount(c);
            // 检查等待队列 读计数上限
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                // 在高 16 位更新
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null ||
                        rh.tid != LockSupport.getThreadId(current))
                        // cachedHoldCounter 每个线程自己的读计数,非共享。但是锁计数与其它读操作共享,不与写操作共享
                        // readHolds 为ThreadLocalHoldCounter,继承于 ThreadLocal,存 cachedHoldCounter
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            // 说明在排队中,就一直遍历,直到队首,实际起作用的代码和上面代码差不多
            // 大师本人也说了代码有冗余
             /*
             * This code is in part redundant with that in
             * tryAcquireShared but is simpler overall by not
             * complicating tryAcquireShared with interactions between
             * retries and lazily reading hold counts.
             */
            return fullTryAcquireShared(current);
        }
        
    // 获得写锁  
    protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail. 
             * 读锁不为零(读锁排除写锁,但是与读锁共享)
             * 写锁不为零且锁持有者不为当前线程,则获得锁失败
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.) // 计数器已达最大值,获得锁失败
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner. // 重入是可以的;队列策略也是可以的,会在下面解释
             */
            Thread current = Thread.currentThread();
            int c = getState();
            // 获得写计数
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                // 检查所持有线程
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                // 检查最大计数
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire 线程重入获得锁,直接更新计数
                setState(c + acquires);
                return true;
            }
            // 队列策略
            // state 为 0,检查是否需要排队
            // 针对公平锁:去排队,如果当前线程在队首或等待队列为空,则返回 false,自然会走后面的 CAS
            // 否则就返回 true,则进入 return false;
            // 针对非公平锁:写死为 false,直接 CAS
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 设置当前写锁持有线程
            setExclusiveOwnerThread(current);
            return true;
        }    
    
    // 因为读锁是多个线程共享读计数,各自维护了自己的读计数,所以释放的时候比写锁释放要多些操作
     protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            // 当前线程是第一读线程的操作
            // firstReader 作为字段缓存,是考虑到第一次读的线程使用率高?
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null ||
                    rh.tid != LockSupport.getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }
复制代码

in conclusion

Fair and unfair lock lock "lock" implementation is based on CASfairness based internally maintained Nodelist

Read-write locks, read and write can be roughly understood as two states, so here's design is similar to the thread pool status. But, read count can be read multiple threads share (excluding write lock), each read the thread count will maintain their own reading. Write locks, then exclusive write count, eliminate all other threads.

Guess you like

Origin juejin.im/post/5d033362f265da1b8b2b5a7c