多线程中使用 Lock 接口的两个实现类 ReentrantLock 和 ReentrantReadWriteLock 实现线程锁

本文介绍了Java中的ReentrantLock和ReentrantReadWriteLock,它们分别实现了Lock接口和ReadWriteLock接口。ReentrantLock是独占锁,提供公平与非公平的选择,而ReentrantReadWriteLock允许多个读取线程并发,但写锁是独占的。两者都支持可重入特性,并且ReentrantReadWriteLock允许写锁降级。Condition接口提供了比Object的wait/notify更细粒度、更高效率的线程通信方式。文章还给出了使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考博客:Java中ReentrantLock的使用(优快云)

参考博客:Java并发编程之ReentrantReadWriteLock详解(优快云)

参考博客:ReentrantReadWriteLock读写锁详解(博客园)

参考博客:ReentrantReadWriteLock读写锁详解(知乎)

ReentrantLock 可重入锁实现了 Lock 接口,是一种独占锁。公平与否取决于构造函数。

ReentrantReadWriteLock 可重入锁实现了 ReadWriteLock 接口,ReentrantReadWriteLock 读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。公平与否取决于构造函数。

ReentrantReadWriteLock 中有两个内部静态类 ReadLock 和 WriteLock,他们都实现了Lock接口。

Lock接口中的方法:

    // 不可中断的方式获得锁,获得锁后线程继续执行,否则阻塞,无法被interrput()方法中断。
    void lock();
	
    // 可中断的方式获得锁,获得锁后线程继续执行,否则阻塞,可以被interrput()方法中断。
    void lockInterruptibly() throws InterruptedException;

    // 尝试获得锁,获得锁以后返回ture,获取锁失败返回false。
    boolean tryLock();

    // 尝试获得锁,获得锁以后返回ture,超时以后返回false。
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 释放锁。
    void unlock();
	
    // 创建Condition,用于多线程间通信。
    Condition newCondition();

ReentrantLock中除实现Lock接口外的一些其他方法:

    // 构造函数,创建一个非公平锁。
    ReentrantLock() 

    // 构造函数,fair = true:创建一个公平锁;fair = false:创建一个非公平锁。
    ReentrantLock(boolean fair) 
    
    // 查看正在抢锁的线程数量。
    int getQueueLength()
	
    // 是否有线程正在抢锁。
    boolean hasQueuedThreads()
	
    // 指定的线程是否在抢锁。
    boolean hasQueuedThread(Thread thread)
	
    // 当前线程是否抢到锁。返回0代表没有。
    int getHoldCount()
	
    // 当前是否有某个线程占有锁。
    boolean isLocked()
	 
    // 是否为公平锁
    boolean isFair() 
	

ReentrantLock.Condition是在粒度和性能上都优于Object的notify()、wait()、notifyAll()线程通信的方式。
Condition中通信方法相对Object的通信在粒度上是粒度更细化,表现在一个Lock对象上引入多个Condition监视器、通信方法中除了和Object对应的三个基本函数外,更是新增了线程中断、阻塞超时的函数;
Condition中通信方法相对Object的通信在性能上更高效,性能的优化表现在ReentrantLock比较synchronized的优化 ;
注意在使用Condition的await()、signal()、signalAll()方法时不能和Object的wait()、notify()、notifyAll()混用,否则抛出IllegalMonitorStateException;

Condition接口中的一些方法:

    // 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
    // 可以被interrupt()方法中断,抛出InterruptedException异常,捕获异常后线程继续执行。
    // 可以被signal()方法或者signalAll()方法唤醒,线程继续执行。
    void await() throws InterruptedException;

    // 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
    // 不可以被interrupt()方法中断。
    // 可以被signal()方法或者signalAll()方法唤醒,线程继续执行。
    void awaitUninterruptibly();

    // 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
    // 可以被interrupt()方法中断,抛出InterruptedException异常,捕获异常后线程继续执行。
    // 可以被signal()方法或者signalAll()方法唤醒,线程继续执行,返回nanosTimeout减去实际等待的时间(单位:纳秒)(一般为正值)
    // 阻塞时间到期后,线程继续执行,返回nanosTimeout减去实际等待的时间(单位:纳秒)(一般为负值)
    long awaitNanos(long nanosTimeout) throws InterruptedException;

    // 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
    // 可以被interrupt()方法中断,抛出InterruptedException异常,捕获异常后线程继续执行。
    // 可以被signal()方法或者signalAll()方法唤醒,线程继续执行,返回true。
    // 阻塞时间到期后,线程继续执行,返回false。
    boolean await(long time, TimeUnit unit) throws InterruptedException;

    // 类似await(long time, TimeUnit unit) 
    boolean awaitUntil(Date deadline) throws InterruptedException;

    // 唤醒指定线程。
    void signal();
	
    // 唤醒全部线程。
    void signalAll();

ReentrantLock 使用 Demo:

    static Integer i = 1;
    
    public static void main(String[] args) {
        // 定义锁
        ReentrantLock lock = new ReentrantLock();
        // 用于多线程间通信
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        
        Thread thread1 = new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 线程开始执行。");
            try {
                lock.lock();
                System.out.println(name + " 线程获得锁。");
                for (; i <= 6;) {
                    for (int j = 1; j <= 2100000000; j++) {
                        if (j == 2100000000) {
                            System.out.println(name + " 线程完成了第 " + i + " 次伟大的循环。");
                        }
                    }
                    i++;
                    // 线程间通信非常耗费性能。一次线程间通信的时间大约会耗费3次伟大的循环的时间。
                    // 唤醒使用condition2阻塞的线程
                    condition2.signal();
                    // 使用condition1阻塞当前线程
                    condition1.await();
                }
                // 唤醒使用condition2阻塞的线程
                condition2.signal();
            } catch (InterruptedException e) {
                System.out.println(name + " 线程阻塞被中断。");
            } finally {
                lock.unlock();
            }
            System.out.println(name + " 线程执行完毕。");
        },"thread1");
        
        Thread thread2 = new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 线程开始执行。");
            try {
                lock.lock();
                System.out.println(name + " 线程获得锁。");
                for (; i <= 6;) {
                    for (int j = 1; j <= 2100000000; j++) {
                        if (j == 2100000000) {
                            System.out.println(name + " 线程完成了第 " + i + " 次伟大的循环。");
                        }
                    }
                    i++;
                    // 唤醒使用condition1阻塞的线程
                    condition1.signal();
                    // 使用condition2阻塞当前线程
                    condition2.await();
                }
                // 唤醒使用condition1阻塞的线程
                condition1.signal();
            } catch (InterruptedException e) {
                System.out.println(name + " 线程阻塞被中断。");
            } finally {
                lock.unlock();
            }
            System.out.println(name + " 线程执行完毕。");
        },"thread2");
        
        thread1.start();
        thread2.start();
        
    }

ReentrantReadWriteLock 实现了 ReadWriteLock,其内部的一些属性、方法和内部类:

    // 读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;

    // 写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;

    final Sync sync;

    // 默认创建非公平锁
    public ReentrantReadWriteLock() {
        this(false);
    }

    // fair = true:创建一个公平锁,fair = false:创建一个非公平锁。
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    // 获得写锁
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    
    // 获得读锁
    public ReentrantReadWriteLock.ReadLock readLock()  { return readerLock; }

    // 是否公平
    public final boolean isFair() {}

    // 持有读锁的线程数量
    public int getReadLockCount() {}

    // 是否有线程持有写锁
    public boolean isWriteLocked() {}

    // 写锁是否由当前线程持有
    public boolean isWriteLockedByCurrentThread() {}

    // 当前线程获得写锁的数量(因为可重入),如果写锁未被当前线程持有,则为零
    public int getWriteHoldCount() {}

    // 当前线程获得读锁的数量(因为可重入),如果读锁未被当前线程持有,则为零
    public int getReadHoldCount() {}

    // 是否有线程正在尝试获得读锁或写锁
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    // 指定线程是否正在尝试获得读锁或写锁
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }

    // 等待获取写锁或读锁的线程数量(估计值)
    public final int getQueueLength() {}

    // 查询是否有线程正在等待与写锁相关联的给定条件
    public boolean hasWaiters(Condition condition) {}

    // 返回与写锁相关联的给定条件下等待的线程数的估计值
    public int getWaitQueueLength(Condition condition) {}

    // toString "写锁数量,读锁数量"
    public String toString() {
        int c = sync.getCount();
        int w = Sync.exclusiveCount(c); // 独占锁数量
        int r = Sync.sharedCount(c);    // 共享锁数量
        return super.toString() + "[Write locks = " + w + ", Read locks = " + r + "]";
    }

    // 指定线程的 ThreadId
    static final long getThreadId(Thread thread) {
        return UNSAFE.getLongVolatile(thread, TID_OFFSET);
    }

    /** 内部类 **/

    abstract static class Sync extends AbstractQueuedSynchronizer {}

    static final class NonfairSync extends Sync {}

    static final class FairSync extends Sync {}

    public static class ReadLock implements Lock, java.io.Serializable {}

    public static class WriteLock implements Lock, java.io.Serializable {}
    
    /** 受保护的方法,外部无法访问 **/

    // 返回获得写锁或正在尝试获得写锁的线程。
    protected Thread getOwner() {}

    // 返回正在尝试获得写锁的线程的集合
    protected Collection<Thread> getQueuedWriterThreads() {}

    // 返回正在尝试获得读锁的线程的集合
    protected Collection<Thread> getQueuedReaderThreads() {}

    // 返回正在尝试获得写锁或读锁的线程的集合
    protected Collection<Thread> getQueuedThreads() {}

    // 返回与写锁相关联的给定条件下等待的线程的集合
    protected Collection<Thread> getWaitingThreads(Condition condition) {}

    ReentrantReadWriteLock 的两个内部类:ReadLock 和 WriteLock 都实现了 Lock 接口,拥有 Lock 接口的方法。并且ReadLock 和 WriteLock 两个内部类中都存在 Sync sync 属性,ReadLock 和 WriteLock 重写 Lock 接口的方法时都是在操作 sync 对象。Sync 是ReentrantReadWriteLock 的一个内部抽象类,他还有两个子类:FairSync(公平锁)NonfairSync(非公平所)

    读锁 ReadLock 是一种共享锁,允许多线程进入;但是如果某个线程获得了写锁,或者正在调用写数据的请求,其余线程不可进入读锁;如果当前线程获得了写锁,当前线程可以进入读锁。

    写锁 WriteLock 是一种排它锁,只允许一个线程进入;如果其他线程进入了读锁或者写锁,当前线程不可进入写锁;如果当前线程进入了读锁,当前线程不可进入写锁。

    ReentrantReadWriteLock 的可重入性表现在:读线程在获得了读锁后可以继续获得读锁,写线程在获得了写锁以后可以继续获得写锁或读锁。ReentrantReadWriteLock 允许写锁降级为读锁(获取写锁后,再获得读锁,然后释放写锁),但是读锁不能升级为写锁。写锁可以获得 Conditon 进行线程间通信,读锁不可以获得 Conditon 进行线程间通信,readLock().newCondition() 会抛出 UnsupportedOperationException。

    ReentrantReadWriteLock 适用于读取数据频率较高但写数据频率低,然而写数据又对读数据有影响的情况下。

    ReentrantReadWriteLock 使用 Demo:

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static WriteLock writeLock = rwl.writeLock();
    static ReadLock readLock = rwl.readLock();
    
    public static void main(String[] args) {
        
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                for (int i=1; i <= 2; i++) {
                    for (int j = 1; j <= 2100000000; j++) {
                        if (j == 2100000000) {
                            System.out.println(name + " 线程正在执行。");
                        }
                    }
                }
                // 获取读锁
                readLock.lock();
                for (int i=1; i <= 2; i++) {
                    for (int j = 1; j <= 2100000000; j++) {
                        if (j == 2100000000) {
                            System.out.println(name + " 线程持有读锁,正在读取数据。是否有线程持有写锁:" + rwl.isWriteLocked());
                        }
                    }
                }
                // 获取读锁后可重入读锁
                getData(name);
                // 释放读锁
                readLock.unlock();
                // 获取写锁
                writeLock.lock();
                for (int i=1; i <= 2; i++) {
                    for (int j = 1; j <= 2100000000; j++) {
                        if (j == 2100000000) {
                            System.out.println(name + " 线程持有写锁,正在写数据。持有读锁的线程数量:" + rwl.getReadLockCount() + ",是否有线程持有写锁:" + rwl.isWriteLocked());
                        }
                    }
                }
                // 获取写锁后可重入读锁
                getData(name);
                // 获取写锁后可重入写锁
                setData(name);
                System.out.println(name + " 线程写锁准备降级为读锁");
                // 获取读锁
                readLock.lock();
                // 释放写锁,实现写锁降级为读锁
                writeLock.unlock();
                System.out.println(name + " 线程写锁已经降级为读锁");
                for (int j = 1; j <= 2100000000; j++) {
                    if (j == 2100000000) {
                        System.out.println(name + " 线程持有读锁,正在读取数据。是否有线程持有写锁:" + rwl.isWriteLocked());
                    }
                }
                // 释放读锁
                readLock.unlock();
            }
        };
        
        new Thread(runnable,"thread1").start();
        new Thread(runnable,"thread2").start();
        new Thread(runnable,"thread3").start();
        new Thread(runnable,"thread4").start();
        
    }
    
    // 读取数据,使用读锁
    public static Object getData(String name) {
        readLock.lock();
        for (int j = 1; j <= 2100000000; j++) {
            if (j == 2100000000) {
                System.out.println(name + " 线程持有getData()读锁,正在读数据。是否有线程持有写锁:" + rwl.isWriteLocked());
            }
        }
        readLock.unlock();
        return null;
    }
    
    // 写数据,使用写锁
    public static void setData(String name) {
        writeLock.lock();
        for (int j = 1; j <= 2100000000; j++) {
            if (j == 2100000000) {
                System.out.println(name + " 线程持有setData()写锁,正在写数据。持有读锁的线程数量:" + rwl.getReadLockCount() + ",是否有线程持有写锁:" + rwl.isWriteLocked());
            }
        }
        writeLock.unlock();
    }

 

### PyCharm 打开文件显示全的解决方案 当遇到PyCharm打开文件显示全的情况时,可以尝试以下几种方法来解决问题。 #### 方法一:清理缓存并重启IDE 有时IDE内部缓存可能导致文件加载异常。通过清除缓存再启动程序能够有效改善此状况。具体操作路径为`File -> Invalidate Caches / Restart...`,之后按照提示完成相应动作即可[^1]。 #### 方法二:调整编辑器字体设置 如果是因为字体原因造成的内容显示问题,则可以通过修改编辑区内的文字样式来进行修复。进入`Settings/Preferences | Editor | Font`选项卡内更改合适的字号大小以及启用抗锯齿功能等参数配置[^2]。 #### 方法三:检查项目结构配置 对于某些特定场景下的源码视图缺失现象,可能是由于当前工作空间未能正确识别全部模块所引起。此时应该核查Project Structure的Content Roots设定项是否涵盖了整个工程根目录;必要时可手动添加遗漏部分,并保存变更生效[^3]。 ```python # 示例代码用于展示如何获取当前项目的根路径,在实际应用中可根据需求调用该函数辅助排查问题 import os def get_project_root(): current_file = os.path.abspath(__file__) project_dir = os.path.dirname(current_file) while not os.path.exists(os.path.join(project_dir, '.idea')): parent_dir = os.path.dirname(project_dir) if parent_dir == project_dir: break project_dir = parent_dir return project_dir print(f"Current Project Root Directory is {get_project_root()}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值