java并发编程(三十六)——初识读写锁

本文详细介绍了读写锁的工作原理及其在多线程环境下的应用。通过对比ReentrantLock,阐述了读写锁如何提高程序并发效率,尤其是在读多写少的场景中。并通过实例演示了读写锁的具体使用。

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

前言

在没有读写锁之前,我们假设使用普通的 ReentrantLock,那么虽然我们保证了线程安全,但是也浪费了一定的资源,因为如果多个读操作同时进行,其实并没有线程安全问题,我们可以允许让多个读操作并行,以便提高程序效率。

但是写操作不是线程安全的,如果多个线程同时写,或者在写的同时进行读操作,便会造成线程安全问题。

我们的读写锁就解决了这样的问题,它设定了一套规则,既可以保证多个线程同时读的效率,同时又可以保证有写入操作时的线程安全。

我们来学习下读写锁的规则及用法。

读写锁

简介

读写锁的整体思路是它有两把锁,第 1 把锁是写锁,获得写锁之后,既可以读数据又可以修改数据,而第 2 把锁是读锁,获得读锁之后,只能查看数据,不能修改数据。读锁可以被多个线程同时持有,所以多个线程可以同时查看数据。

在读的地方合理使用读锁,在写的地方合理使用写锁,灵活控制,可以提高程序的执行效率。

获取规则

我们在使用读写锁时遵守下面的获取规则:

  1. 如果有一个线程已经占用了读锁,则此时其他线程如果要申请读锁,可以申请成功。

  2. 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁,因为读写不能同时操作。

  3. 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,都必须等待之前的线程释放写锁,同样也因为读写不能同时,并且两个线程不应该同时写。

所以我们用一句话总结:要么是一个或多个线程同时有读锁,要么是一个线程有写锁,但是两者不会同时出现。也可以总结为:读读共享、其他都互斥(写写互斥、读写互斥、写读互斥)。

代码案例

下面我们举个例子来应用读写锁,ReentrantReadWriteLock 是 ReadWriteLock 的实现类,最主要的有两个方法:readLock() 和 writeLock() 用来获取读锁和写锁。

代码如下:

package com.concurrency;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    private static final ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock(false);

    private static final ReentrantReadWriteLock.ReadLock readLock=reentrantReadWriteLock.readLock();

    private static final ReentrantReadWriteLock.WriteLock writeLock=reentrantReadWriteLock.writeLock();

    private static void read(){
        readLock.lock();
        System.out.println(Thread.currentThread().getName()+"得到读锁,正在读取");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+"释放读锁");
            readLock.unlock();
        }
    }

    private static void write(){
        writeLock.lock();
        System.out.println(Thread.currentThread().getName()+"得到写锁,正在写");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+"释放写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(()->read()).start();
        new Thread(()->read()).start();
        new Thread(()->write()).start();
        new Thread(()->write()).start();
    }

}

执行结果:

Thread-0得到读锁,正在读取
Thread-1得到读锁,正在读取
Thread-0释放读锁
Thread-1释放读锁
Thread-2得到写锁,正在写
Thread-2释放写锁
Thread-3得到写锁,正在写
Thread-3释放写锁

可以看出:读锁可以同时被多个线程获得,而写锁不能

问题:

读数据不会有线程安全问题,但为什么要加读锁呢

加读锁,不是因为读取有安全问题,而是为了让程序感知到,此时正在读,不要同时来写入。

例如对于共享变量的读,又存在写操作的话,假如读操作不加锁,读的过程中,共享变量是允许其他线程修改的,那么就可能发生问题,读到的不是原期望值。

还有一个思路,就是我们之前说到过的CopyOnWrite思想,如果读不加锁的话,使用CopyOnWrite思想的话,读操作就读副本,写操作的话就写原始数据,等写入操作完成在将副本更新,这种思想在CopyOnWriteArrayList 中有实际应用。CopyOnWrite的好处是可以并发读写,缺点是不能保证读取的是最新的数据。相比于CopyOnWrite,读写锁可以保证读取到的一定是最新的数据,但不能并发读写。这是两种不同的思想,他们都有不同的应用场景。

使用场景

相比于 ReentrantLock 适用于一般场合,ReadWriteLock 适用于读多写少的情况,合理使用可以进一步提高并发效率。

总结

我们主要学习了读写锁的思想,主要就是读读操作可以并行,其他都不允许并行操作。我们对读写锁和CopyOnWrite思想进行了比较,各自有各自的优缺点,适用于不同的应用场景,我们需要根据实际的场景灵活选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员资料站

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值