java多线程:详解使用ReentrantReadWriteLock读写锁,以及锁降级

1.ReentrantReadWriteLock的特性说明

1.1获取锁顺序

  • 非公平模式(默认):当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。
  • 公平模式:当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。

1.2可重入

  • 什么是可重入锁,不可重入锁呢?"重入"字面意思已经很明显了,就是可以重新进入。可重入锁,就是说一个线程在获取某个锁后,还可以继续获取该锁,即允许一个线程多次获取同一个锁。

1.3锁降级

  • 什么是锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。
  • 注意点:锁降级之后,写锁并不会直接降级成读锁,不会随着读锁的释放而释放,因此需要显式地释放写锁 。
  • 锁降级的应用场景:用于对数据比较敏感,需要在对数据修改之后,获取到修改后的值,并进行接下来的其他操作
  • 是否有锁升级:ReentrantReadWriteLock里面,不存在锁升级的说法。

1.4读写锁特性

  • 写写互斥,读写互斥,读读共享

2.模式多线程读写锁实例

public class ReentrantReadWriteLockDemo {

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock ();

    private Lock readLock = readWriteLock.readLock ();

    private Lock writeLock = readWriteLock.writeLock ();

    private int i = 0;

    private int j = 0;

    public void readMethod() {
        // 读锁
        readLock.lock ();
        try {
            System.out.println (String.format ("线程%s获得i======》%d,获得j======》%d", Thread.currentThread ().getName (), i, j));
        } finally {
            // 释放锁
            readLock.unlock ();
        }
    }

    public void writeMethod() {
        // 写锁
        writeLock.lock ();
        try {
            i++;
            j++;
            System.out.println (String.format ("线程%s正在写入i====>%d,j=====>%d", Thread.currentThread ().getName (), i, j));
        } finally {
            // 释放锁
            writeLock.unlock ();
        }
    }

    public static void main(String[] args) {
        ReentrantReadWriteLockDemo readWriteLockDemo = new ReentrantReadWriteLockDemo ();

        // 模拟十个写入线程
        for (int i = 0; i < 10; i++) {
            new Thread (() -> {
                readWriteLockDemo.writeMethod ();
            }).start ();
        }

        // 模拟三个读线程
        for (int j = 0; j < 3; j++) {
            new Thread (() -> {
                readWriteLockDemo.readMethod ();
            }).start ();
        }
    }

}
  • 控制台输出:

ps:ReentrantReadWriteLock是ReadWriteLock接口的实现,ReadWriteLock提供了两个接口readLock和writeLock;通过调用readLock()和writeLock()方法直接返回了readLock和writeLock两个实例对象。ReadLock和WriteLock是ReentrantReadWriteLock下面的静态内部类。而这两个静态内部类都实现了Lock接口。调用lock方法之后就用到了AQS的doAcquireShared()方法。AQS用单一的int值表示读写值位置,int是32位,将其才分成无符号的两个short,高位表示读锁,低位表示写锁。两个锁的最大次数为2的16次方减1。

3.模拟锁降级

public class ReentrantReadWriteLockDemo2 {

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock ();

    /** 读锁 */
    private Lock readLock = readWriteLock.readLock ();

    /** 写锁 */
    private Lock writeLock = readWriteLock.writeLock ();

    public static void main(String[] args) {

        ReentrantReadWriteLockDemo2 readWriteLockDemo2 = new ReentrantReadWriteLockDemo2 ();

        // 获取写锁
        readWriteLockDemo2.writeLock.lock ();
        // 执行写入操作
        System.out.println ("开始写入");
        
        // 获取读锁  PS:没有释放写锁,直接获取读锁,是锁降级
        readWriteLockDemo2.readLock.lock ();
        // 释放写锁  PS:如果写入一直没被释放,会导致线程死锁
        readWriteLockDemo2.writeLock.unlock ();
        // 执行读取操作
        System.out.println ("开始读取");
        // 释放读锁
        readWriteLockDemo2.readLock.unlock ();
        
        
        // 创建一个线程模拟继续写入,如果在锁降级后没有释放写锁,由于写写互斥,造成线程死锁
        new Thread (() ->{
            readWriteLockDemo2.writeLock.lock ();
            System.out.println ("继续写入");
        }).start ();


    }

}
  • 控制台输出:

ps:读写锁特性:写写互斥、读写互斥、读读共享

4.演示锁升级错误操作

public class ReentrantReadWriteLockDemo3 {

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock ();

    private Lock readLock = readWriteLock.readLock ();

    private Lock writeLock = readWriteLock.writeLock ();

    public static void main(String[] args) {
        ReentrantReadWriteLockDemo3 readWriteLockDemo3 = new ReentrantReadWriteLockDemo3 ();

        // 获取读锁
        readWriteLockDemo3.readLock.lock ();
        // 执行读操作
        System.out.println ("开始读取");

        /**
         * 获取写锁  PS:在没有释放读锁,获取写锁,为锁升级操作,
         *          ReentrantReadWriteLock不支持锁升级,线程死锁
         */
        readWriteLockDemo3.writeLock.lock ();
        // 开始写入
        System.out.println ("开始写入");
        // 释放写操作
        readWriteLockDemo3.writeLock.unlock ();
    }


}
  • 控制台输出:

  • 使用jconsole查看:

5.总结

  • ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
  • ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁。写写互斥、读写互斥、读读共享
  • ReetrantReadWriteLock实现了ReadWriteLock接口并实现了可重入的特性
  •  ReetrantReadWriteLock读锁的效率明显高于synchronized关键字,因为ReetrantReadWriteLock读锁是共享模式,运行线程并发执行,而synchronized只能让拥有锁的线程去读取,所以读取的效率没有ReetrantReadWriteLock高。

链接: java多线程:详解StampedLock的特性,悲观读和乐观读,排他写

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值