Java Concurrency

锁 - 独占 vs 共享

2022-11-24  本文已影响0人  面向对象架构

独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,是独享锁。

独占锁

典型的独占锁:synchronized、vector、hashtable、ReentrantReadWriteLock中的写锁

该锁每一次只能被一个线程所持有。

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {

    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

    /** Performs all synchronization mechanics */
    final Sync sync;

    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; }


    /**
     * The lock returned by method {@link ReentrantReadWriteLock#writeLock}.
     */
    public static class WriteLock implements Lock, java.io.Serializable {
        private final Sync sync;

        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        public void lock() {
            // 这里是重点
            sync.acquire(1);
        }
    }

    public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;

        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        public void lock() {
            // 这里是重点
            sync.acquireShared(1);
        }
    }

    /**
     * Synchronization implementation for ReentrantReadWriteLock.
     * Subclassed into fair and nonfair versions.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {

        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) {
                // c != 0 && w == 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;
            }
            // 是否需要阻塞
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            // 设置获取锁的线程为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }

        // 因为存在锁降级情况,如果存在写锁且锁的持有者不是当前线程,则直接返回失败,否则继续。
        // 依据公平性原则,调用 readerShouldBlock() 方法来判断读锁是否不需要阻塞,
        //     读锁持有线程数小于最大值(65535),且 CAS 设置锁状态成
        protected final int tryAcquireShared(int unused) {
            /*
             * 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();
            // exclusiveCount(c)计算写锁
            // 如果存在写锁,且锁的持有者不是当前线程,直接返回-1
            // 存在锁降级问题,后续阐述
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // 读锁
            int r = sharedCount(c);
            // readerShouldBlock():读锁是否需要等待(公平锁原则)
            // r < MAX_COUNT:持有线程小于最大数(65535)
            // compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) { // 修改高16位的状态,所以要加上2^16
                // r==0 说明读本线程是第一个获取读锁的线程
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                // firstReader==current 说明此次获取读锁为firstReader的重入
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    // 判断成功说明当前缓存的HoldCounter不是本线程的
                    if (rh == null ||
                        rh.tid != LockSupport.getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    // rh.count==0 时有可能本线程对应的ThreadLocalMap中已经没有了rh这个键
                    // 为了保险其见需要重新配置一下
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

    }

}

ReentrantReadWriteLock 有两把锁:ReadLock和WriteLock,可知,一个读锁,一个写锁合称“读写锁”。 ReadLock 和 WriteLock 是靠内部类 Sync 实现的锁。

Sync 是 AQS 的一个子类,这种结构在 CountDownLatch 、ReentrantLock 、Semaphore 里面也都存在。

在ReentrantReadWriteLock 里面,读锁和写锁的锁主体都是 Sync ,但读锁和写锁的加锁方式不一样。
读锁是共享锁,写锁是独享锁

共享锁

典型的共享锁:ReentrantReadWriteLock中的读锁

该锁可被多个线程所持有。

典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。

对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁

读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

AQS

抽象队列同步器(AbstractQueuedSynchronizer,简称AQS)是用来构建锁或者其他同步组件的基础框架,它使用一个整型的volatile变量(命名为state)来维护同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

AQS、非阻塞数据结构和原子变量类等基础类都是基于volatile变量的读/写和CAS实现,而像Lock、同步器、阻塞队列、Executor和并发容器等高层类又是基于基础类实现。

上一篇 下一篇

猜你喜欢

热点阅读