读写锁ReentrantReadWriteLock

在多线程的环境下,线程对数据的操作可以分为两大类:读和写。

很显然这两种操作对数据的影响程度不同:对数据的读操作只是读取数据的值,并不会影响数据本身;而对数据的写操作需要对数据进行修改,将会涉及到数据的安全问题,会影响其他线程对数据的读、写操作。(可以参考数据库的事务锁进行理解)

正因为读和写操作对数据的安全性程度要求有所差别,我们可以将读和写的同步控制分开,分别用读锁和写锁进行同步控制。

前面我们已经熟悉的ReentrantLock和synchronized内部锁都是将读和写操作都锁定,在java.util.concurrent.locks包下还有一个读/写锁ReentrantReadWriteLock。

ReentrantReadWriteLock

读写锁维护了两个锁:读取锁和写入锁。读取锁可以由多个 reader 线程同时保持,而写入锁是独占的,只允许一个写线程持有。

一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。

用法

(1)先构造一个ReentrantReadWriteLock对象:

private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

(2)抽取读锁和写锁:

private lock readlock = rwl.readLock();
private lock writeLock = rwl.writeLock();

(3)对所有的获取方法加读锁,对所有的写方法加写锁。

例如,将之前的银行转账的例子中的Bank类加以改造成使用ReentrantReadWriteLock读写锁进行同步控制:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Bank {
	
	private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();//构造ReentrantReadWriteLock对象
	Lock readLock = rwl.readLock();//获取读锁
	Lock writeLock = rwl.writeLock();//获取写锁
	
	/**
	 * 转账
	 */
	public boolean transfer(Account fromAccount, Account toAccount, double money) throws InterruptedException {
		writeLock.lock();//给写方法加写锁
try {
			fromAccount.setMoney(fromAccount.getMoney() - money);
			toAccount.setMoney(toAccount.getMoney() + money);
			System.out.println(fromAccount.getName() + "向" + toAccount.getName() + "转账" + money + "元");

			return true;
		}finally {
			writeLock.unlock();
		}

	}

	/**
	 * 打印余额
	 * 
	 * @param account 账户
	 */
	public void display(Account account) {
		readLock.lock();//给读方法加读锁
		try {
			System.out.println(account.getName() + ":" + account.getMoney() + "元");
		}finally {
			readLock.unlock();
		}
	}
}

特性

(一) 公平模式
ReentrantReadWriteLock读写锁不会将读锁优先或写锁优先,但是它支持可选的公平策略。
在这里插入图片描述

(二) 重入
此锁允许reader和writer按照ReentrantLock的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入reader使用它们。

此外,writer可以获取读取锁,但反过来则不成立。在其他应用程序中,当在调用或回调那些在读取锁状态下执行读取操作的方法期间保持写入锁时,重入很有用。如果reader 试图获取写入锁,那么将永远不会获得成功。

(三) 锁降级
重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的

例如,在转账方法transfer中进行锁降级,可以正常的运行:

public boolean transfer(Account fromAccount, Account toAccount, double money) throws InterruptedException {
		try {
			writeLock.lock();//给写方法加写锁
			fromAccount.setMoney(fromAccount.getMoney() - money);
			toAccount.setMoney(toAccount.getMoney() + money);
			System.out.println(fromAccount.getName() + "向" + toAccount.getName() + "转账" + money + "元");

			readLock.lock();//获取读锁
			System.out.println(fromAccount.getName() + ":" + fromAccount.getMoney() + "元");
			
			return true;
		}finally {
			writeLock.unlock();
			readLock.unlock();
		}

	}

在这里插入图片描述

我们试一下在打印余额方法display中进行“锁升级”,即:先获取读取锁,然后获取写入锁,最后释放读取锁。

public void display(Account account) {
		readLock.lock();//给读方法加读锁
		try {
			System.out.println(account.getName() + ":" + account.getMoney() + "元");
			writeLock.lock();//获取写锁
		}finally {
			readLock.unlock();
			writeLock.unlock();
		}
	}

前面说过ReentrantReadWriteLock进行锁升级是不行的,虽然编译器不会报错,但是程序在运行的过程中会出现死锁的情况。
在这里插入图片描述

(四) 支持condition
写入锁提供了一个Condition实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition()提供的Condition 实现对ReentrantLock所做的行为相同。当然,此 Condition 只能用于写入锁。

源码与实现分析

ReentrantReadWriteLock实现了接口ReadWriteLock,该接口提供了两个方法,一个用于获取读锁,另一个用于获取写锁。
在这里插入图片描述

ReentrantReadWriteLock中定义了两个静态内部类,分别对应读锁和写锁。

在这里插入图片描述在这里插入图片描述
这两个类都实现了Lock接口,从源码中可以看出,读锁、写锁的操作都是依靠Sync类来实现的,而Sync只是一个抽象类,所以同步的具体实现都是通过NonfairSync和FairSync类。所以ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样而已。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值