基于Redission高级应用13-RReadWriteLock原理及工具类封装及实战应用

概述:

RReadWriteLock 是一个在分布式环境中使用的读写锁,它通常是由分布式缓存或数据存储系统(如Redisson)提供的。这个锁允许多个读操作同时进行,而写操作则是互斥的。

原理:

  1. 读写锁分离: 读写锁允许多个读操作同时进行,只要没有写操作。当写锁被持有时,其他的读写操作都会被阻塞。
  2. 锁降级: 写锁持有者可以降级为读锁,这是通过首先获取读锁,然后释放写锁来完成的。这允许锁的持有者继续以读取者的身份持有锁。
  3. 锁升级: 通常,读锁不能直接升级为写锁,因为这可能会导致死锁。如果需要写入,读锁持有者必须释放读锁并独立获取写锁。
  4. 分布式环境 : 在分布式系统中,RReadWriteLock 通常使用分布式存储(如Redis)来保持锁的状态。 锁的状态需要跨多个节点同步,以确保锁的一致性和正确性。
  5. 公平性: 某些实现支持公平锁,这意味着锁的分配是按照请求的顺序来的,以避免饥饿问题。

优点:

  1. 提高并发性: 读写锁允许多个读操作并发执行,这在读多写少的场景中可以显著提高性能。
  2. 资源共享: 读锁的共享性质允许更多的线程同时访问共享资源。
  3. 分布式支持RReadWriteLock 适用于分布式系统,可以在多个节点间同步锁的状态。
  4. 死锁减少: 相比于传统的互斥锁,读写锁在某些情况下可以减少死锁的可能性。

缺点:

  1. 复杂性: 读写锁的管理比简单的互斥锁更复杂,需要正确处理读和写锁的交互。
  2. 性能开销: 分布式锁需要网络通信来同步状态,这可能增加延迟和性能开销。
  3. 写锁饥饿: 在高读负载的情况下,写锁可能会遭受饥饿,因为总是有新的读锁被获取。
  4. 设计考虑: 在设计分布式系统时,必须考虑锁的粒度和持有时间,以避免性能瓶颈。
  5. 一致性挑战: 在分布式环境中保持锁状态的一致性可能是一个挑战,尤其是在网络分区或节点故障的情况下。

使用场景:

  1. 缓存系统 :在读多写少的缓存系统中,RReadWriteLock 可以用于保护缓存的一致性。读操作可以并发执行,而写操作(更新缓存)则需要独占锁。

  2. 系统配置 :当系统配置需要被多个服务实例共享并可能被更新时,RReadWriteLock 可以用来保证配置更新的原子性和一致性。

  3. 数据库访问 :在执行复杂的事务或批量操作时,RReadWriteLock 可以用来防止其他操作干扰这些操作。

  4. 分布式计算 :在分布式计算任务中,RReadWriteLock 可以用来同步不同计算节点间的状态

RReadWriteLock流程图:

基于 RReadWriteLock 的锁获取和释放流程:

flowchart LR A[Start] --> B{Get RReadWriteLock} B -- Get Read Lock --> C[RLock readLock = readWriteLock.readLock] B -- Get Write Lock --> D[RLock writeLock = readWriteLock.writeLock] C --> E{Try to Acquire Read Lock} D --> F{Try to Acquire Write Lock} E -- Lock Acquired --> G[Perform Read Operations] F -- Lock Acquired --> H[Perform Write Operations] G --> I[Release Read Lock] H --> J[Release Write Lock] I --> K[End] J --> K E -- Lock Not Acquired --> L[Handle Read Lock Failure] F -- Lock Not Acquired --> M[Handle Write Lock Failure] L --> K M --> K

这个流程图中:

  • 开始节点 "Start" 表示流程的开始。
  • "Get RReadWriteLock" 是一个决策节点,用于获取读锁或写锁。
  • "RLock readLock = readWriteLock.readLock()" 和 "RLock writeLock = readWriteLock.writeLock()" 表示获取读锁或写锁的操作。
  • "Try to Acquire Read Lock" 和 "Try to Acquire Write Lock" 是尝试获取锁的决策节点。
  • "Perform Read Operations" 和 "Perform Write Operations" 表示在锁保护下执行的操作。
  • "Release Read Lock" 和 "Release Write Lock" 表示释放锁的操作。
  • "Handle Read Lock Failure" 和 "Handle Write Lock Failure" 表示处理获取锁失败的情况。
  • "End" 表示流程的结束。

RReadWriteLock时序图:

基于 RReadWriteLock 的锁获取和释放的交互过程:

sequenceDiagram participant Client as Client participant RReadWriteLock as RReadWriteLock participant ReadLock as ReadLock participant WriteLock as WriteLock Client->>RReadWriteLock: getReadWriteLock(lockKey) alt Acquiring Read Lock RReadWriteLock->>ReadLock: readLock() Client->>ReadLock: tryLock(timeout, timeUnit) ReadLock-->>Client: Lock Acquired Client->>ReadLock: performReadOperations() Client->>ReadLock: unlock() else Acquiring Write Lock RReadWriteLock->>WriteLock: writeLock() Client->>WriteLock: tryLock(timeout, timeUnit) WriteLock-->>Client: Lock Acquired Client->>WriteLock: performWriteOperations() Client->>WriteLock: unlock() end

这个时序图中:

  • "Client" 是发起锁请求的客户端或者代码。
  • "RReadWriteLock" 是 Redisson 提供的读写锁的抽象。
  • "ReadLock" 和 "WriteLock" 分别代表读锁和写锁。

时序图展示了以下步骤:

  1. 客户端请求从 RReadWriteLock 获取读写锁(getReadWriteLock(lockKey))。
  2. 根据需要获取读锁或写锁的路径,时序图分为两个分支:
    • 在获取读锁的分支中,RReadWriteLock 提供了读锁对象(readLock()),客户端尝试获取读锁(tryLock(timeout, timeUnit)),如果成功,执行读操作,然后释放锁(unlock())。
    • 在获取写锁的分支中,RReadWriteLock 提供了写锁对象(writeLock()),客户端尝试获取写锁,如果成功,执行写操作,然后释放锁。

RReadWriteLock 类图:

classDiagram class RLock { +lock() +unlock() +tryLock() bool } class RReadWriteLock { +readLock() RLock +writeLock() RLock } class RedissonClient { +getReadWriteLock(String name) RReadWriteLock } RReadWriteLock --|> RLock : Inherits RedissonClient ..> RReadWriteLock : Creates

这个类图中:

  • RLock 是一个接口或抽象类,提供了锁的基本操作,如 lock()unlock()tryLock()
  • RReadWriteLock 是一个接口,提供了获取读锁和写锁的方法,这两个方法返回 RLock 类型的对象。
  • RedissonClient 是一个类,它提供了获取 RReadWriteLock 实例的方法 getReadWriteLock()

关系说明:

  • RReadWriteLock 继承自 RLock(在这个示例中,这是一个简化的假设,实际上 RReadWriteLock 可能不会直接继承 RLock,但会提供访问读锁和写锁的方法,这两种锁都实现了 RLock 接口)。
  • RedissonClient 创建 RReadWriteLock 实例的关系用虚线箭头表示。

ReadWriteLock 工具类

DistributedReadWriteLockHelper

基于RReadWriteLock 这边封装了一个功能较为完整的工具类:

java 复制代码
import lombok.Data;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @Author derek_smart
 * @Date 202/5/10 8:41
 * @Description ReadWriteLock 工具类
 */
@Component
public class DistributedReadWriteLockHelper {
    @Autowired
    private RedissonClient redissonClient;


    public LockHandler acquireReadLock(String lockKey) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);
        RLock readLock = readWriteLock.readLock();
        readLock.lock();
        return new LockHandler(readLock);
    }

    public LockHandler acquireWriteLock(String lockKey) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);
        RLock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        return new LockHandler(writeLock);
    }

    public LockHandler tryAcquireReadLock(String lockKey, long timeout, TimeUnit timeUnit) throws InterruptedException {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);
        RLock readLock = readWriteLock.readLock();
        if (readLock.tryLock(timeout, timeUnit)) {
            return new LockHandler(readLock);
        }
        return null;
    }

    public LockHandler tryAcquireWriteLock(String lockKey, long timeout, TimeUnit timeUnit) throws InterruptedException {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);
        RLock writeLock = readWriteLock.writeLock();
        if (writeLock.tryLock(timeout, timeUnit)) {
            return new LockHandler(writeLock);
        }
        return null;
    }

    public LockHandler tryAcquireWriteLockWithFallback(String lockKey, long timeout, TimeUnit timeUnit, Supplier<LockHandler> fallbackSupplier) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);
        RLock writeLock = readWriteLock.writeLock();
        try {
            if (writeLock.tryLock(timeout, timeUnit)) {
                return new LockHandler(writeLock);
            } else {
                return fallbackSupplier.get();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Thread interrupted while trying to acquire write lock", e);
        }
    }

    /**
     * 尝试在指定的超时时间内获取写锁,并且锁具有租约时间,超过租约时间后锁会自动释放。这可以防止在节点故障时发生死锁。
     * 同时,`LockHandler` 类中添加了 `renewLock` 方法,用于在锁快到期时续约锁。
     *
     * @param lockKey
     * @param acquireTimeout
     * @param lockLeaseTime
     * @param timeUnit
     * @return
     */
    public LockHandler tryAcquireWriteLockWithTimeout(String lockKey, long acquireTimeout, long lockLeaseTime, TimeUnit timeUnit) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);
        RLock writeLock = readWriteLock.writeLock();
        try {
            if (writeLock.tryLock(acquireTimeout, lockLeaseTime, timeUnit)) {
                return new LockHandler(writeLock);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Thread interrupted while trying to acquire write lock", e);
        }
        return null;
    }

    // Method to try to acquire a lock with retry logic
    public LockHandler tryAcquireLockWithRetry(String lockKey, boolean writeLock, LockConfig lockConfig, LockCallback callback) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);
        RLock lock = writeLock ? readWriteLock.writeLock() : readWriteLock.readLock();
        int attempts = lockConfig.getDefaultRetryAttempts();
        long interval = lockConfig.getDefaultRetryInterval();

        while (attempts > 0) {
            try {
                if (lock.tryLock(lockConfig.getDefaultAcquireTimeout(), lockConfig.getDefaultLeaseTime(), lockConfig.getDefaultTimeUnit())) {
                    if (callback != null) {
                        callback.onLockAcquired();
                    }
                    return new LockHandlerN(lock, lockConfig);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
            attempts--;
            try {
                TimeUnit.SECONDS.sleep(interval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        return null;
    }


    public static class LockHandlerN extends LockHandler implements AutoCloseable {
        private final RLock lock;
        private final LockConfig lockConfig;

        private LockHandlerN(RLock lock, LockConfig lockConfig) {
            super();
            this.lock = lock;
            this.lockConfig = lockConfig;
        }

        @Override
        public void close() {
            try {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                    // Log unlock event
                }
            } catch (Exception e) {
                // Log exception
            }
        }

        public void renewLockIfNecessary() {
            try {
                if (lock.isHeldByCurrentThread() && !lock.isLocked()) {
                    lock.lock(lockConfig.getDefaultLeaseTime(), lockConfig.getDefaultTimeUnit());
                    // Log renew event
                }
            } catch (Exception e) {
                // Log exception
            }
        }

    }

    public static class LockHandler implements AutoCloseable {
        private RLock lock;

        private LockHandler(RLock lock) {
            this.lock = lock;
        }

        public LockHandler() {

        }

        @Override
        public void close() {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

        public boolean isLocked() {
            return lock.isLocked();
        }

        // 可以添加方法支持锁的升级和降级
        public void upgrade() {
            // 实现从读锁升级到写锁的逻辑
        }

        // 实现从写锁降级到读锁的逻辑
        public void downgrade(RReadWriteLock readWriteLock) {
            if (!isHeldByCurrentThread()) {
                throw new IllegalStateException("Cannot downgrade a lock not held by current thread");
            }
            RLock readLock = readWriteLock.readLock();
            readLock.lock();
            lock.unlock();
        }


        public boolean isHeldByCurrentThread() {
            return lock.isHeldByCurrentThread();
        }

        // 可以添加续约方法,如果需要的话
        public void renewLock(long leaseTime, TimeUnit timeUnit) {
            if (isHeldByCurrentThread()) {
                lock.lock(leaseTime, timeUnit);
            }
        }

    }

    @Data
    public class LockConfig {
        private long defaultAcquireTimeout = 30;
        private long defaultLeaseTime = 60;
        private TimeUnit defaultTimeUnit = TimeUnit.SECONDS;
        private int defaultRetryAttempts = 3;
        private long defaultRetryInterval = 5;
    }

/*    public interface LockCallback {
        void onLockAcquired();
    }*/
}

真正使用时,请把LockCallback LockConfig 单独封装一个类,这边是为了实现一个类显示所有。

DistributedReadWriteLockTestService测试类:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;

import java.util.concurrent.TimeUnit;

/**
 * @Author derek_smart
 * @Date 202/5/10 9:41
 * @Description ReadWriteLock 测试类
 */
public class DistributedReadWriteLockTestService {
    @Autowired
    private DistributedReadWriteLockHelper lockHelper;

    public void performReadOperation(String lockKey) {
        // 获取读锁
        DistributedReadWriteLockHelper.LockHandler readLockHandler = lockHelper.acquireReadLock(lockKey);
        try {
            // 执行需要读锁保护的操作
        } finally {
            // 释放读锁
            readLockHandler.close();
        }
    }

    public void performWriteOperation(String lockKey) {
        // 获取写锁
        DistributedReadWriteLockHelper.LockHandler writeLockHandler = lockHelper.acquireWriteLock(lockKey);
        try {
            // 执行需要写锁保护的操作
        } finally {
            // 释放写锁
            writeLockHandler.close();
        }
    }

    public void performReadOperationWithTimeout(String lockKey, long timeout, TimeUnit timeUnit) {
        // 尝试获取读锁,带有超时
        try {
            DistributedReadWriteLockHelper.LockHandler readLockHandler = lockHelper.tryAcquireReadLock(lockKey, timeout, timeUnit);
            if (readLockHandler != null) {
                try {
                    // 执行需要读锁保护的操作
                } finally {
                    // 释放读锁
                    readLockHandler.close();
                }
            } else {
                // 未能获取读锁
            }
        } catch (InterruptedException e) {
            // 处理中断异常
            Thread.currentThread().interrupt();
        }
    }

    public void performWriteOperationWithTimeout(String lockKey, long timeout, TimeUnit timeUnit) {
        // 尝试获取写锁,带有超时
        try {
            DistributedReadWriteLockHelper.LockHandler writeLockHandler = lockHelper.tryAcquireWriteLock(lockKey, timeout, timeUnit);
            if (writeLockHandler != null) {
                try {
                    // 执行需要写锁保护的操作
                } finally {
                    // 释放写锁
                    writeLockHandler.close();
                }
            } else {
                // 未能获取写锁
            }
        } catch (InterruptedException e) {
            // 处理中断异常
            Thread.currentThread().interrupt();
        }
    }

    public void performWriteOperationWithFallback(String lockKey, long timeout, TimeUnit timeUnit) {
        // 尝试获取写锁,如果失败则使用回退策略
        DistributedReadWriteLockHelper.LockHandler writeLockHandler = lockHelper.tryAcquireWriteLockWithFallback(lockKey, timeout, timeUnit, () -> {
            // 提供回退策略的锁处理器
            return null; // 或者实现一个备用的 LockHandler
        });

        if (writeLockHandler != null) {
            try {
                // 执行需要写锁保护的操作
            } finally {
                // 释放写锁
                writeLockHandler.close();
            }
        } else {
            // 回退策略被触发,未能获取写锁
        }
    }
}

DistributedReadWriteLockHelper总结

  • 基本功能:提供了获取读锁和写锁的基本方法。
  • 超时处理:支持尝试获取锁时带有超时设置。
  • 回退策略:允许在无法获取写锁时提供一个回退策略。
  • 重试逻辑:提供了在获取锁失败时进行重试的方法。
  • 锁处理器 :通过 LockHandler 类管理锁的释放,实现了 AutoCloseable 接口,方便在 try-with-resources 语句中自动释放锁。

进一步优化的建议

以上实现的DistributedReadWriteLockHelper 有一定可取性和一定弊端,大家再使用时根据自己情况进一步的优化,以下就是因为时间原因没有实现自身

  1. 异常处理
    • 自定义异常类,代替 RuntimeException,以提供更多关于锁获取失败的上下文信息。
  2. 配置灵活性
    • 允许更灵活的配置选项,例如通过外部配置文件设置默认的超时时间、重试次数等。
  3. 锁的状态监控
    • 实现一个监控系统,记录锁的获取和释放事件,以及失败次数和原因,有助于故障排查和系统监控。在必要时续约,以避免在长时间操作中锁过期。
  4. 锁的升级和降级
    • 提供安全的锁升级(从读锁到写锁)和降级(从写锁到读锁)机制,确保不会导致死锁。
  5. 性能优化
    • 分析锁操作的性能,并根据实际使用情况进行优化,以减少锁的等待时间和提高系统吞吐量。
  6. 可观测性和日志记录
    • 增强日志记录,记录关键操作和异常情况,以及提供可观测性工具,如指标和仪表板,以便于系统的监控和警报。
  7. 分布式锁的最佳实践
    • 确保遵循分布式锁的最佳实践,例如避免长时间持有锁,最小化锁范围,以及在合适的时机释放锁。
  8. 锁的公平性
    • 如果业务场景需要,可以提供公平锁的配置选项,以确保线程按照请求锁的顺序获得锁。

通过实施这些建议,可以提高 DistributedReadWriteLockHelper 的可用性、可靠性和性能,从而更好地服务于需要分布式锁功能的应用程序。

总结:

RReadWriteLock 是 Redisson 提供的一个接口,模拟了Java并发包中的 java.util.concurrent.locks.ReadWriteLock 接口,但是它是为分布式环境设计的。DistributedReadWriteLockHelper 是一个围绕 RReadWriteLock 提供的辅助工具,旨在简化分布式锁的管理,而 RReadWriteLock 是Redisson实现的一个接口,提供分布式读写锁的核心功能。

相关推荐
.生产的驴1 分钟前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript
爱学的小涛9 分钟前
【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解
java·开发语言·笔记·后端·nio
吹老师个人app编程教学10 分钟前
详解Java中的BIO、NIO、AIO
java·开发语言·nio
爱学的小涛10 分钟前
【NIO基础】NIO(非阻塞 I/O)和 IO(传统 I/O)的区别,以及 NIO 的三大组件详解
java·开发语言·笔记·后端·nio
北极无雪15 分钟前
Spring源码学习:SpringMVC(4)DispatcherServlet请求入口分析
java·开发语言·后端·学习·spring
琴智冰18 分钟前
SpringBoot
java·数据库·spring boot
binqian19 分钟前
【SpringSecurity】基本流程
java·spring
小登ai学习42 分钟前
简单认识 redis -3 -其他命令
数据库·redis·缓存
猿小蔡-Cool1 小时前
CPU 多级缓存
java·spring·缓存
gopher95111 小时前
final,finally,finalize的区别
java·开发语言·jvm