Redisson的读写锁RReadWriteLock写写锁、写读锁使用不互斥阻塞问题

本文探讨了Redisson的读写锁RReadWriteLock在写写锁和写读锁使用时出现的非互斥阻塞问题,发现原因是lockWatchdogTimeout设置。当程序执行超过此超时时间,会导致锁不互斥。测试结果显示,锁的互斥行为取决于lockWatchdogTimeout的配置。

一. 读写锁RReadWriteLock写写锁、写读锁使用不互斥阻塞问题

在测试redisson的读写锁RReadWriteLock时,发现写写锁、写读锁不互斥阻塞。同样的线程代码在测试JUC下的读写锁ReentrantReadWriteLock,则不存在写写锁、写读锁不互斥阻塞问题。
一度怀疑redisson版本问题,换了几个版本存在同样的问题。 后来发现程序执行时间超过锁定看门狗超时时间(config.setLockWatchdogTimeout),会引起写写锁、写读锁不互斥阻塞. 这个问题找了N久!!!!!!

二. lockWatchdogTimeout 参数作用

如果获取锁的 Redisson 实例崩溃,则此类锁可以永远处于获取状态。为了避免这种情况 Redisson 维护锁看门狗,它会在锁持有人 Redisson 实例处于活动状态时延长锁过期时间。默认情况下,锁定看门狗超时为 30 秒,可以通过 Config.lockWatchdogTimeout 设置进行更改。

三. 测试代码

RedissonUtil

public class RedissonUtil {
    public static RedissonClient redissonClient;
    static {
        Config config = new Config();
        config.setLockWatchdogTimeout(60) //锁定看门狗超时设置
                .useSingleServer()
                .setAddress("redis://192.168.64.130:6379")
                .setDatabase(1);
        redissonClient = Redisson.create(config);
    }

    public RedissonClient getRedissonClient() {
        return redissonClient;
    }
}

BaseTest

public class BaseTest {
    protected RedissonClient redissonClient;
    @Before
    public  void init(){
        RedissonUtil redissonUtil = new RedissonUtil();
        redissonClient = redissonUtil.getRedissonClient();
    }

    protected void sleep(long time){
        try {
            Thread.sleep(time);
            System.out.println(Thread.currentThread().getName()+" 线程等待,"+ time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

TestReentrantReadWriteLock

public class TestReentrantReadWriteLock extends BaseTest {
    static String us ="00000";
    @Test
    public void  testReadWriteLock(){
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(false);

        Thread thread1 = new Thread(()->runWriteLock(readWriteLock));
        Thread thread2 = new Thread(()->runReadLock(readWriteLock));
        Thread thread3 = new Thread(()->runReadLock(readWriteLock));
        Thread thread4 = new Thread(()->runWriteLock(readWriteLock));
        Thread thread5 = new Thread(()->runReadLock(readWriteLock));

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

        while (true){
        }
    }

    private void runReadLock(ReentrantReadWriteLock readWriteLock) {
        long start = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 开始尝试取读锁,"+start);
        ReentrantReadWriteLock.ReadLock rLock = readWriteLock.readLock();

        try {
            boolean res = rLock.tryLock(10L, TimeUnit.SECONDS);
            if(res){
                //成功获得锁,在这里处理业务
                System.out.println(Thread.currentThread().getName()+" 获取读锁成功,"+(System.currentTimeMillis() - start)+","+us);
                sleep(5000);
            }
        } catch (Exception e) {
            System.out.println("获取读锁失败,失败原因:" + e.getMessage());
        } finally {
            //无论如何, 最后都要解锁
            rLock.unlock();
            System.out.println(Thread.currentThread().getName()+" 解读锁成功,"+(System.currentTimeMillis() - start)+"\n");
        }
    }

    private void runWriteLock(ReentrantReadWriteLock readWriteLock) {
        long start = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 开始尝试取-写锁,"+start);
        ReentrantReadWriteLock.WriteLock rLock = readWriteLock.writeLock();
        try {
            boolean res = rLock.tryLock(10L, TimeUnit.SECONDS);
            if(res){
                //成功获得锁,在这里处理业务
                System.out.println(Thread.currentThread().getName()+" 获取--写锁--成功,"+(System.currentTimeMillis() - start)+","+us);
                us = Thread.currentThread().getName();

                //等待,占用锁5S
                sleep(5000);
            }
        } catch (Exception e) {
            System.out.println("获取写锁失败,失败原因:" + e.getMessage());
        } finally {
            //无论如何, 最后都要解锁
            rLock.unlock();
            System.out.println(Thread.currentThread().getName()+" 解--写锁--成功,"+(System.currentTimeMillis() - start)+"\n");
        }
    }
}

四. 测试结果

当程序执行时间超过锁定看门狗超时时间,会引起写写、写读不互斥阻塞

config.setLockWatchdogTimeout(60)  写写、写读不会互斥,如setLockWatchdogTimeout时间为60s,但示例中程序超过了60,先获取的写锁提前失效,后面线程可以继续获取

当程序执行时间未超过锁定看门狗超时时间,写写、写读会互斥阻塞

config.setLockWatchdogTimeout(600)  

写锁+写锁 测试

  • 执行时间超过锁定看门狗超时时间,不互斥阻塞
Thread-1 开始尝试取-写锁,1688541911818
Thread-4 开始尝试取-写锁,1688541911818
Thread-1 获取--写锁--成功,21,00000
Thread-4 获取--写锁--成功,82,Thread-1
Thread-1 线程等待,5000
Thread-1--写锁--成功,5034  //写线程1 执行5s

Thread-4 线程等待,5000
Thread-4--写锁--成功,5094  //写线程4 执行5s,说明未阻塞
  • 执行时间未超过锁定看门狗超时时间,互斥阻塞
Thread-1 开始尝试取-写锁,1688541587287
Thread-4 开始尝试取-写锁,1688541587287
Thread-1 获取--写锁--成功,16,00000
Thread-1 线程等待,5000
Thread-1--写锁--成功,5026 //写线程1 执行5s

Thread-4 获取--写锁--成功,5032,Thread-1
Thread-4 线程等待,5000
Thread-4--写锁--成功,10038 //写线程4 执行10s,说明被阻塞

写锁+读锁 测试

  • 执行时间超过锁定看门狗超时时间,不互斥阻塞
Thread-1 开始尝试取-写锁,1688541770214
Thread-2 开始尝试取读锁,1688541770214
Thread-1 获取--写锁--成功,20,00000
Thread-2 线程等待,1000
Thread-2 获取读锁成功,1105,Thread-1
Thread-1 线程等待,5000
Thread-1--写锁--成功,5027 //写线程1 执行5s

Thread-2 线程等待,5000
Thread-2 解读锁成功,6119  //读线程2 执行6s,说明未阻塞
  • 执行时间未超过锁定看门狗超时时间,互斥阻塞
Thread-1 开始尝试取-写锁,1688542009667
Thread-2 开始尝试取读锁,1688542009667
Thread-1 获取--写锁--成功,20,00000
Thread-1 线程等待,5000
Thread-1--写锁--成功,5037 //写线程1 执行5s

Thread-2 线程等待,1000
Thread-2 获取读锁成功,6052,Thread-1
Thread-2 线程等待,5000
Thread-2 解读锁成功,11067 //读线程2 执行11s,说明阻塞

读锁+读锁 测试

  • 执行时间超过锁定看门狗超时时间,不互斥阻塞
Thread-2 开始尝试取读锁,1688542266988
Thread-3 开始尝试取读锁,1688542266989
Thread-2 线程等待,1000
Thread-2 获取读锁成功,1031,00000
Thread-3 线程等待,1000
Thread-3 获取读锁成功,1030,00000
Thread-3 线程等待,5000
Thread-2 线程等待,5000
Thread-3 解读锁成功,6048   //读线程3 执行6s

Thread-2 解读锁成功,6049  //读线程3 执行6s,说明未阻塞
  • 执行时间未超过锁定看门狗超时时间,不互斥阻塞
Thread-2 开始尝试取读锁,1688542186099
Thread-3 开始尝试取读锁,1688542186099
Thread-3 线程等待,1000
Thread-2 线程等待,1000
Thread-2 获取读锁成功,1035,00000
Thread-3 获取读锁成功,1035,00000
Thread-2 线程等待,5000
Thread-3 线程等待,5000
Thread-2 解读锁成功,6044 //读线程3 执行6s

Thread-3 解读锁成功,6044  //读线程3 执行6s,说明未阻塞
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值