1780 字
约 5 分钟
11
Java JUC教程第6章:读写锁

Java JUC教程第6章:读写锁

章节头部 第6章

🔒 读写锁

掌握读写分离的高效并发控制机制,理解ReentrantReadWriteLock和StampedLock的原理与应用
学习目标

学习目标

  • 理解读写锁的设计原理和适用场景
  • 掌握ReentrantReadWriteLock的使用方法
  • 了解锁的升级和降级机制
  • 学习StampedLock的高级特性
  • 进行读写锁的性能分析和优化

读写锁概述

读写锁概述

读写锁(ReadWriteLock)是一种特殊的锁机制,它允许多个线程同时获取读锁,但只允许一个线程获取写锁。这种设计基于一个重要的观察:在很多应用场景中,读操作远比写操作频繁,而多个读操作之间并不会产生数据竞争。

核心思想

读写锁通过读写分离的策略,在保证数据一致性的前提下,显著提高了并发读取的性能。

读写锁的特性

共享读锁多个线程可以同时获取读锁,实现并发读取,提高系统吞吐量。独占写锁只有一个线程可以获取写锁,确保写操作的原子性和数据一致性。互斥性读锁和写锁之间是互斥的,写锁和写锁之间也是互斥的。

适用场景

  • 缓存系统:频繁的读取操作,偶尔的更新操作
  • 配置管理:配置信息读取频繁,修改较少
  • 统计数据:数据查询多,数据更新少
  • 资源池:资源获取频繁,资源管理操作较少

ReentrantReadWriteLock详解

ReentrantReadWriteLock详解

ReentrantReadWriteLock是Java中ReadWriteLock接口的标准实现,它提供了可重入的读写锁功能。该类内部维护了两个锁:一个读锁和一个写锁,它们共享同一个同步状态。

基本用法

ReentrantReadWriteLock基本示例

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    
    private String data = "初始数据";
    
    // 读操作
    public String readData() {
        readLock.lock();
        try {
            // 模拟读取操作
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " 读取数据: " + data);
            return data;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        } finally {
            readLock.unlock();
        }
    }
    
    // 写操作
    public void writeData(String newData) {
        writeLock.lock();
        try {
            // 模拟写入操作
            Thread.sleep(2000);
            this.data = newData;
            System.out.println(Thread.currentThread().getName() + " 写入数据: " + data);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            writeLock.unlock();
        }
    }
}

锁的特性

可重入性同一个线程可以多次获取同一个锁,避免死锁问题。公平性支持公平和非公平两种模式,可在构造时指定。超时获取支持tryLock()方法,可设置超时时间避免无限等待。 锁的升级和降级

锁的升级和降级

ReentrantReadWriteLock支持锁降级但不支持锁升级。锁降级是指持有写锁的线程可以获取读锁,然后释放写锁,从而将写锁降级为读锁。

重要提醒

ReentrantReadWriteLock不支持锁升级(从读锁升级到写锁),这样设计是为了避免死锁。

锁降级示例

锁降级实现

public class LockDowngradeExample {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    
    private volatile boolean dataChanged = false;
    private String data = "初始数据";
    
    public void processData() {
        readLock.lock();
        try {
            if (!dataChanged) {
                // 需要更新数据,先释放读锁
                readLock.unlock();
                
                // 获取写锁
                writeLock.lock();
                try {
                    // 双重检查
                    if (!dataChanged) {
                        data = "更新后的数据";
                        dataChanged = true;
                    }
                    
                    // 锁降级:在释放写锁之前获取读锁
                    readLock.lock();
                } finally {
                    writeLock.unlock(); // 释放写锁
                }
            }
            
            // 使用数据(持有读锁)
            System.out.println("处理数据: " + data);
            
        } finally {
            readLock.unlock();
        }
    }
}

锁降级的优势

  • 数据一致性:确保在数据更新后立即读取到最新数据
  • 性能优化:避免释放写锁后其他线程修改数据
  • 原子操作:保证更新和读取操作的原子性

StampedLock高级特性

StampedLock高级特性

StampedLock是Java 8引入的新锁机制,它提供了三种锁模式:写锁、悲观读锁和乐观读。相比ReentrantReadWriteLock,StampedLock在读多写少的场景下性能更优。

三种锁模式

写锁独占锁,与ReentrantReadWriteLock的写锁类似,但不可重入。悲观读锁共享锁,与ReentrantReadWriteLock的读锁类似,但不可重入。乐观读无锁操作,通过版本号检查数据是否被修改,性能最优。

StampedLock使用示例

StampedLock完整示例

import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private final StampedLock lock = new StampedLock();
    private double x, y;
    
    // 写操作
    public void write(double newX, double newY) {
        long stamp = lock.writeLock();
        try {
            x = newX;
            y = newY;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
    
    // 乐观读
    public double distanceFromOrigin() {
        long stamp = lock.tryOptimisticRead();
        double curX = x, curY = y;
        
        // 检查在读取过程中是否有写操作
        if (!lock.validate(stamp)) {
            // 乐观读失败,降级为悲观读
            stamp = lock.readLock();
            try {
                curX = x;
                curY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        
        return Math.sqrt(curX * curX + curY * curY);
    }
    
    // 悲观读
    public double[] getCurrentPoint() {
        long stamp = lock.readLock();
        try {
            return new double[]{x, y};
        } finally {
            lock.unlockRead(stamp);
        }
    }
    
    // 读锁升级为写锁
    public void moveIfAtOrigin(double newX, double newY) {
        long stamp = lock.readLock();
        try {
            while (x == 0.0 && y == 0.0) {
                // 尝试将读锁升级为写锁
                long writeStamp = lock.tryConvertToWriteLock(stamp);
                if (writeStamp != 0L) {
                    stamp = writeStamp;
                    x = newX;
                    y = newY;
                    break;
                } else {
                    // 升级失败,释放读锁,获取写锁
                    lock.unlockRead(stamp);
                    stamp = lock.writeLock();
                }
            }
        } finally {
            lock.unlock(stamp);
        }
    }
}

StampedLock的优势

  • 更高性能:乐观读模式避免了锁竞争,提高并发性能
  • 锁转换:支持锁模式之间的转换,提供更大的灵活性
  • 无饥饿:写锁不会被读锁无限期阻塞
  • 内存效率:相比ReentrantReadWriteLock占用更少内存

性能分析与最佳实践

性能分析与最佳实践

选择合适的读写锁实现对系统性能有重要影响。不同的锁机制在不同场景下表现各异,需要根据具体的应用特点进行选择。

性能对比

读多写少场景StampedLock > ReentrantReadWriteLock > synchronized读写均衡场景ReentrantReadWriteLock ≈ StampedLock > synchronized写多读少场景synchronized ≈ ReentrantReadWriteLock ≈ StampedLock

最佳实践

使用建议

场景选择:读多写少场景优先考虑StampedLock可重入需求:需要可重入特性时选择ReentrantReadWriteLock公平性要求:需要公平锁时使用ReentrantReadWriteLock简单场景:简单的互斥场景可以使用synchronized

注意事项

  • 避免锁升级:ReentrantReadWriteLock不支持从读锁升级到写锁
  • 正确释放锁:使用try-finally确保锁的正确释放
  • 避免长时间持锁:减少锁的持有时间,提高并发性能
  • 合理使用乐观读:在StampedLock中合理使用乐观读模式
  • 监控锁竞争:定期监控锁的竞争情况,及时优化

章节导航 上一章:Lock接口 返回目录 下一章:Condition条件

Java JUC教程第6章:读写锁
https://clxhxhhr.top/posts/3021/
作者
clxstart
发布于
2026-09-24
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。