Redisson读写锁

Redisson读写锁

在看Redis和mysql双写一致性时看到了使用Redisson读写锁达到强一致性。

介绍

双写一致性流程:

线程T1,T2。写锁:wLock;读锁:rLock。

T1修改数据库:先获取wLock,然后修改数据库数据,修改之后删除Redis中缓存。

T2读取缓存内容:先获取rLock,由于T1未释放wLock,获取锁失败,被阻塞。阻塞直至T1释放wLock,T2获取到rLock【此时任何线程想获取同一把写锁将被阻塞】,T2读取Redis发现没有数据,于是去数据库中读取最新数据。

使用读写锁虽然能达到一致性,但是性能较差。如果一致性要求不高,可以通过延时双删、MQ异步删除Redis数据、Canal方式实现

Canal 是阿里巴巴开源的一款基于数据库日志增量订阅&消费的解决方案,主要用于实时同步数据库变更到下游存储(如缓存、搜索引擎、数据仓库等)。它基于数据库的日志实现了增量数据订阅和消费,可以将数据库的变更操作(如插入、更新、删除)实时推送给其他数据存储或应用系统,实现数据的实时同步和消息传递。

SpringBoot项目中引入依赖

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.16.0</version>  
</dependency>
<dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.HashMap;
import java.util.concurrent.CountDownLatch;

说明:

读写锁使用Redisson

设置和获取键值使用springboot提供的RedisTemplate

流程:

使用两个线程分别执行读和写操作,调整线程优先级:

如果先执行写,写锁是排他锁,其余线程不能进行读写操作。写方法内部在设置key-value之后会等待1秒,观察这1秒内,读线程不能执行。

如果先执行读,读锁是共享锁,其余线程可读,但不能写。读方法内部在读取值之后,等待1秒,观察这1秒内,写线程不能执行。

当读,写线程都执行完毕之后,关闭Redisson客户端。(CountDownLatch)

具体代码实现

public class RedissonReadWriteLockExample {


    static RedissonClient redissonClient = getRedissonClient();

    static CountDownLatch countDownLatch = new CountDownLatch(2);//等待读、写线程执行完毕后,关闭redis客户端

    RedisTemplate<String, String> redisTemplate = getRedisTemplate();//用于进行key的设置和读取
    RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");//获取redisson读写锁;

    RLock readLock = readWriteLock.readLock();
    RLock writeLock = readWriteLock.writeLock();

    /**
     * 获取redisson客户端
     * @return
     */
    private static RedissonClient getRedissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }

    /**
     * 获取redisTemplate
     *
     * @return
     */
    private static RedisTemplate<String, String> getRedisTemplate() {
        //LettuceConnectionFactory使用此配置类
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
        lettuceConnectionFactory.afterPropertiesSet();

        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    /**
     * 读操作
     */

    public void getKey() {
        try {
            readLock.lock();
            String name = redisTemplate.opsForValue().get("name");
            System.out.println("READ_KEY:" + name);
            Thread.sleep(1000);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            readLock.unlock();
            countDownLatch.countDown();
        }
    }

    /**
     * 写操作
     */
    public void setKey() {
        try {
            writeLock.lock();
            redisTemplate.opsForValue().set("name", "zhangsan");
            System.out.println("SET_KEY");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            writeLock.unlock();
            countDownLatch.countDown();
        }
    }

    public static void main(String[] args) {
        RedissonReadWriteLockExample example = new RedissonReadWriteLockExample();
        Thread thread = new Thread(example::getKey);
        Thread thread1 = new Thread(example::setKey);

        /**
         * 控制执行优先级,1代表最低优先级,10代表最高优先级。
         * 但不一定按照优先级顺序执行,看操作系统调度
          */
        thread.setPriority(10); //先读
        thread1.setPriority(1); //后写

//        thread.setPriority(1); //后读
//        thread1.setPriority(10); //先写


        thread.start();
        thread1.start();

        /**
         * 在使用完 Redisson 客户端后,需要显式地关闭它,以确保相关的线程资源被释放
         * 否则程序会一直显示在运行
         */
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        redissonClient.shutdown();
    }
}

Redisson是一个基于Redis的分布式锁和并发数据结构的Java库。它提供了一个简单而强大的API来处理并发问题。 在Redisson中,读写锁是一种特殊类型的锁,用于控制对共享资源的并发读写访问。读写锁的特点是多个读操作可以同时进行,但在写操作时需要排他性。 使用Redisson读写锁非常简单,可以按照以下步骤进行操作: 1. 创建RedissonClient对象,连接到Redis服务器。 2. 获取读写锁对象:RReadWriteLock rwLock = redisson.getReadWriteLock("lockKey"); 3. 获取读锁:RLock readLock = rwLock.readLock(); 4. 获取写锁:RLock writeLock = rwLock.writeLock(); 5. 在需要读取共享资源时,使用readLock.lock()获取读锁,读取完成后使用readLock.unlock()释放读锁。 6. 在需要写入共享资源时,使用writeLock.lock()获取写锁,写入完成后使用writeLock.unlock()释放写锁。 读写锁的使用示例代码如下: ```java // 创建RedissonClient对象 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); // 获取读写锁对象 RReadWriteLock rwLock = redisson.getReadWriteLock("lockKey"); RLock readLock = rwLock.readLock(); RLock writeLock = rwLock.writeLock(); // 读取共享资源 readLock.lock(); try { // 执行读取操作 } finally { readLock.unlock(); } // 写入共享资源 writeLock.lock(); try { // 执行写入操作 } finally { writeLock.unlock(); } // 关闭RedissonClient连接 redisson.shutdown(); ``` 通过使用Redisson读写锁,我们可以实现对共享资源的并发读写访问控制,确保数据的一致性和线程安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值