zoukankan      html  css  js  c++  java
  • ReentrantReadWriteLock读写锁简单原理案例证明

    ReentrantReadWriteLock存在原因?


    我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了List的多线程非安全问题,但是有个缺点:读写同步,效率低下。于是就出现了CopyOnWriteArrayList,它通过写时复制数组实现了读写分离,提高了多线程对List读的效率,适合多读少些的情况。同理:我们知道ReentrantLock,它是一把独占的锁,是用来控制线程同步的,如果我们用ReentrantLock来实现ArrayList安全,能否达到CopyOnWriteArrayList同样的效果呢?

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * @author :jiaolian
     * @date :Created in 2021-01-26 15:49
     * @description:ReentrantReadWriteLock多读少写的场景
     * @modified By:
     * 公众号:叫练
     */
    public class MultReadTest {
    
    
        private static class MyList {
            private final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
            private final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
            private final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();
            private List<String> list = new ArrayList();
    
            //读list
            public void readList() {
                try {
                    READ_LOCK.lock();
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+":"+list.size());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    READ_LOCK.unlock();
                }
            }
    
            //写list
            public void writeList() {
                try {
                    WRITE_LOCK.lock();
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+":新增1个元素");
                    list.add("叫练【公众号】");
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    WRITE_LOCK.unlock();
                }
            }
    
        }
    
        public static void main(String[] args) {
            MyList myList = new MyList();
            //读写锁适合多读少写情况
            //新建10个读线程,1个写线程
            new Thread(()->{myList.writeList();},"写线程").start();
            for (int i=0; i<10; i++) {
                new Thread(()->{myList.readList();},"读线程"+(i+1)).start();
            }
        }
    
    }
    

    上面案例我们用ReentrantReadWriteLock实现了CopyOnWriteArrayList,主线程新建了1个写线程写list,10个读线程读list,程序一共花费2执行完毕,如果用Vector需要花费11秒。在多线程的情况下,通过读写锁操作List,提高了List的读效率,在List读的部分,线程是共享的,在对List写的过程中,在对写的线程是同步的,因此我们可以得出一个结论:读写锁是读读共享,读写同步

    独占获取锁简单流程


    image.png
    如上图,我们简单的梳理下独占获取锁流程。

    1. 独占锁获取(上述例子中的WRITE_LOCK写锁),首先判断是否有线程获取了锁,是否有线程获取了锁的判断通过读写锁中通过32位int类型state可以获取,其中低16位表示读锁,高16表示写锁。
    2. 有读锁:直接排队阻塞。
    3. 有写锁:还需要判断写锁线程是否是自己,如果是自己就是锁重入了,如果不是自己说明已经有其他的线程获取锁正在执行,那么当前线程需要排队阻塞。
    4. 无锁:直接获取锁,其他抢占的独占锁线程需要排队阻塞,当前线程执行完毕后释放锁通知下一个排队线程获取锁。

    共享获取锁简单流程


    image.png
    如上图,我们简单的梳理下共享锁获取锁流程。

    1. 独占锁获取(上述例子中的READ_LOCK读锁),首先判断是否有线程获取了锁。
    2. 有读锁:当前线程发现此时读锁状态被占用,说明有线程获取了读锁。该线程通过cas自旋【死循环】获取到读锁为止。
    3. 有写锁:还需要判断持有写锁的线程是否是自己,如果是自己而且此时是获取的是读锁会获取锁成功,我们称为锁降级,如果不是自己说明此时有其他线程获取了写锁,那么当前线程需要排队阻塞。
    4. 无锁:直接获取锁。

    写锁降级


    我们说读写互斥,但同一个线程中,先写后读也是允许的,我们称之为锁降级。在面试中共享锁面试频率也比较高,方便理解我们举个简单的案例说明下。

    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * @author :jiaolian
     * @date :Created in 2021-01-28 15:44
     * @description:ReentrantReadWriteLock读写锁降级测试
     * @modified By:
     * 公众号:叫练
     */
    public class WriteLockLowerTest {
    
        private static final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
        private static final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
        private static final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();
    
        public static void main(String[] args) {
            try {
                WRITE_LOCK.lock();
                System.out.println("获取写锁");
                READ_LOCK.lock();
                System.out.println("获取读锁");
            } finally {
                READ_LOCK.unlock();
                System.out.println("释放写锁");
                WRITE_LOCK.unlock();
                System.out.println("释放读锁");
            }
        }
    }
    

    如上述代码:程序可以运行完毕,说明锁可以降级。另外说一句,上面的程序先获取读锁再获取写锁,程序是会阻塞的,为什么呢?欢迎小伙伴在留言区写下评论!

    总结


    今天我们用通俗易懂的文字描述了ReentrantReadWriteLock读写锁。喜欢的请点赞加评论哦!点关注,不迷路,我是叫练【公众号】,边叫边练。期待我们下次再见!
    tempimage1611629165941.gif

  • 相关阅读:
    内存寻址:逻辑地址到物理地址的转化
    变量类型,变量作用域,变量存储空间,变量生命周期
    位运算计算与位运算应用
    sizeof()计算
    位域(位段)
    自然对齐和强制对齐
    内存中的数据对齐
    用汇编编写子程序,可以显示字符串到屏幕指定位置
    汇编语言 实验9 根据材料编程
    80x25彩色字符模式
  • 原文地址:https://www.cnblogs.com/liqiangwei/p/14354189.html
Copyright © 2011-2022 走看看