基于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实现的一个接口,提供分布式读写锁的核心功能。

相关推荐
lzb_kkk1 分钟前
【JavaEE】JUC的常见类
java·开发语言·java-ee
爬山算法25 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
编程、小哥哥1 小时前
设计模式之抽象工厂模式(替换Redis双集群升级,代理类抽象场景)
redis·设计模式·抽象工厂模式
2401_857439691 小时前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧6661 小时前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索1 小时前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter
qq_4924484461 小时前
Java实现App自动化(Appium Demo)
java
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
找了一圈尾巴2 小时前
前后端交互通用排序策略
java·交互