美文网首页Java Concurrency
锁 - 独占 vs 共享

锁 - 独占 vs 共享

作者: 面向对象架构 | 来源:发表于2022-11-24 00:15 被阅读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和并发容器等高层类又是基于基础类实现。

    相关文章

      网友评论

        本文标题:锁 - 独占 vs 共享

        本文链接:https://www.haomeiwen.com/subject/uiasxdtx.html