• Java多线程(5)----浅谈读写锁


    1,前言

    ReentrantLock是一个排他锁,这种锁在同一时刻只允许一个线程进行访问。在实际生产中,多个线程同时读一个资源是不会产生并发问题的

    读写锁在同一时刻可以允许多个线程访问,但是在写线程访问时,所有的读线程和其他写线程均会被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁来提高性能。

    讨论读写锁时,会涉及到重入锁

    2,读写锁的实现分析

    2.1,读写状态设计

    读写锁是通过分离读锁与写锁,以实现共享读和独占写

    那么同步状态的设计是一个问题。

    读写锁使用整型变量来维护多个读线程和一个写线程的状态

    读写锁将变量切分成了两个部分,用高16位表示读,低16位表示写

    在这里插入图片描述

    因为读写锁是一个可重入锁,所以这里的读状态统计实际上是多个线程重复获取读锁的次数总和。而每个线程各自获取读锁的次数只能选择保存在ThreadLocal中,由线程自身维护,这使得获取读锁的实现变得复杂。

    读写锁中关于获取状态的相关源代码如下:

    public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
        
        	......
        
            abstract static class Sync extends AbstractQueuedSynchronizer {
                private static final long serialVersionUID = 6317671515068378041L;
    
    
                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; }
    
                ......
                
            }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.2,写锁的获取和释放

    写锁是一个支持重进入的排他锁。如果当前线程获取了写锁,则增加了写状态。

    如果一个线程试图获取写锁时,发现读锁已经被获取;或者线程试图获取写锁时,发现写锁已经被其他线程获取了;这些情况会导致线程被阻塞。

    之所以当获取写锁,而读锁已经被获取时,线程会被阻塞;是因为读写锁要保证写锁的操作对于读锁是可见的。否则就会产生并发问题

    相关的源代码如下:

    public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
        
        	......
        
            abstract static class Sync extends AbstractQueuedSynchronizer {
             
                ......
                
                protected final boolean tryAcquire(int acquires) {
    
                    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)
                        //这里代表着:当前有线程获取了锁,如果w为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;
                }
                
                ......
                
            }
        
        	......
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    读锁的释放与ReentrantLock的释放过程是一致的,有多少次获取该锁,就有对应次数的释放动作。

    2.3,读锁的获取与释放

    读锁是一个支持重进入的共享锁,它能够同时被多个线程所持有,在写锁没有被其他线程访问时,读锁的获取往往是能成功地。

            protected final int tryAcquireShared(int unused) {
                
                //当前线程
                Thread current = Thread.currentThread();
                //获取同步状态
                int c = getState();
                //如果写锁已经被获取并且当前线程跟获取写锁的线程不是同一个
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return -1;
                //获取读的状态值
                int r = sharedCount(c);
                //这一步是成功获取读锁
                if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
                    if (r == 0) {
                        //如果此时读锁并未被获取过
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        //如果是持有读锁的线程重复获取该锁
                        firstReaderHoldCount++;
                    } else {
    
                        ......
                        
                    }
                    return 1;
                }
                return fullTryAcquireShared(current); 
            }
           
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这个方法中,如果其他线程已经获取了写锁,那么当前线程获取读锁时就会失败,并进入等待状态;如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠 CAS 保证)增加读状态,成功获取读锁(获取写锁再然获取读锁,这与锁降级有关)

    2.4,锁降级

    锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,那么在持有写锁的情况下,获取读锁,获取之后再释放写锁,这个过程就叫锁降级。

  • 相关阅读:
    gorm的增删改查
    什么款式的蓝牙耳机跑步不容易掉?推荐几款很不错的运动耳机
    机器学习 | Python线性回归+Ridge正则化
    Python MongoDB
    系统结构设计原则、聚合与耦合
    Python中使用requests库遇到的问题及解决方案
    MediaCodec 同步方式完成AAC硬解成PCM
    F. Selling a Menagerie Codeforces Round 895 (Div. 3)
    Windows安装mysql详细步骤(通俗易懂,简单上手)
    TCP/IP(二)导论
  • 原文地址:https://blog.csdn.net/weixin_41043607/article/details/125449473