读写锁分离
- 场景描述
- 读写锁设计
- 接口定义
- Lock接口
- ReadWriteLock
- 程序实现
- ReadWriteLockImpl
- 写锁
- 读锁
- 读写锁的使用
- 总结
- Reference
场景描述
在多线程的情况下访问共享资源,需要对资源进行同步操作以防止数据不一致的情况。对资源的访问一般包括两种类型的动作——读和写(更新、删除、增加等资源会发生变化的动作)。如果多个线程在某个时刻都在进行对资源的读操作,虽然有资源的竞争,但是这种竞争不足以产生数据不一致的情况发生,这个时候如果直接使用排它的方式加锁,就显得有些简单粗暴。
线程 | 读 | 写 |
读 | 不冲突 | 冲突 |
写 | 冲突 | 冲突 |
如果对某个资源的读操作明显多于写操作,那么多线程读时不加锁,对程序性能的提升会有很大的帮助。
读写锁设计
接口定义
Lock接口
Lock接口定义了锁的基本操作,加锁和解锁。显示锁的操作建议与try finally语句块一起使用。
public interface Lock {
/**
* 获取显式锁,没有获得所得线程被阻塞
* @throws InterruptedException
*/
void lock() throws InterruptedException;
/**
* 释放获取的锁
*/
void unlock();
}
ReadWriteLock
ReadWriteLock并不是Lock,主要是用于创建read lock和write lock,提供查询功能用于查询当前有多少个reader和writer以及waiting中的writer。
public interface ReadWriteLock {
/**
* 创建reader锁
*/
Lock readLock();
/**
* 创建write锁
*/
Lock writeLock();
/**
* 获取当前有多少个线程在执行写操作
*/
int getWritingWriters();
/**
* 获取当前有多少个线程在等待获取写入锁
*/
int getWaitingWriters();
/**
* 获取当前有多少个线程在等待获取reader锁
*/
int getReadingReaders();
/**
* 工厂方法,创建ReadWriteLock
*/
static ReadWriteLock readWriteLock(){
return new ReadWriteLockImpl();
}
/**
* 工厂方法,创建ReadWriteLock,并设置preferWriter
*/
static ReadWriteLock readWriteLock(boolean preferWriter){
return new ReadWriteLockImpl(preferWriter);
}
}
程序实现
ReadWriteLockImpl
ReadWriteLockImpl是一个工厂类,为了减少对外暴露的细节,将其设计为包可见的类。在实现内部,Mutex进行数据同步与线程之间的通信。preferWriter的作用在于控制倾向性,读写锁适用于读多写少的场景,如果preferWriter设置为false,那么写线程可能会发生饥饿。
/**
* default 包可见,创建时使用ReadWriteLockcreate方法
*/
class ReadWriteLockImpl implements ReadWriteLock {
/**
* 定义对象锁
*/
private final Object MUTEX = new Object();
/**
* 当前有多少个线程正在写入
*/
private int writingWriters = 0;
/**
* 当前有多少个线程正在等待写入
*/
private int waitingWriters = 0;
/**
* 当前有多少个对象正在read
*/
private int readingReaders = 0;
/**
* 设置read和write的偏好
*/
private boolean preferWriter;
/**
* 默认情况下preferWriter为true
*/
public ReadWriteLockImpl() {
this(true);
}
/**
* 构建ReadWriteLockImpl,传入preferWriter
* @param preferWriter
*/
public ReadWriteLockImpl(boolean preferWriter) {
this.preferWriter = preferWriter;
}
/**
* 创建read lock
*/
@Override
public Lock readLock() {
return new ReadLock(this);
}
/**
* 创建write lock
*/
@Override
public Lock writeLock() {
return new WriteLock(this);
}
/**
* 使写线程的数量增加
*/
void incrementWritingWriters(){
this.writingWriters++;
}
/**
* 使等待写入的线程数量增加
*/
void incrementWaitingWriters(){
this.waitingWriters++;
}
/**
* 使读线程的数量增加
*/
void incrementReadingReaders(){
this.readingReaders++;
}
/**
* 使写线程的数量减少
*/
void decrementWritingWriters(){
this.writingWriters--;
}
/**
* 使等待获取写入锁的线程数量减少
*/
void decrementWaitingWriters(){
this.waitingWriters--;
}
/**
* 使读取线程的数量减少
*/
void decrementReadingReaders(){
this.readingReaders--;
}
/**
* 获取当前有多少个线程正在进行写操作
*/
@Override
public int getWritingWriters() {
return this.writingWriters;
}
/**
* 获取当前有多少个线程正在等待获取写入锁
*/
@Override
public int getWaitingWriters() {
return this.waitingWriters;
}
/**
* 获取当前有多少个线程正在进行读操作
*/
@Override
public int getReadingReaders() {
return this.readingReaders;
}
/**
* 获取对象锁
*/
Object getMUTEX(){
return this.MUTEX;
}
/**
* 获取当前是否偏向写锁
*/
boolean isPreferWriter(){
return this.preferWriter;
}
/**
* 设置写锁偏好
* @param preferWriter
*/
public void setPreferWriter(boolean preferWriter) {
this.preferWriter = preferWriter;
}
}
写锁
/**
* WriteLock设计为包可见
*/
public class WriteLock implements Lock {
private final ReadWriteLockImpl readWriteLock;
WriteLock(ReadWriteLockImpl readWriteLock) {
this.readWriteLock = readWriteLock;
}
@Override
public void lock() throws InterruptedException {
synchronized (readWriteLock.getMUTEX()) {
try {
// 使等待获取写入锁的线程数量增加
readWriteLock.incrementWaitingWriters();
// 如果此时有其他线程正在进行读操作,或者写操作,当前线程挂起
while (readWriteLock.getReadingReaders() > 0 || readWriteLock.getWritingWriters() > 0) {
readWriteLock.getMUTEX().wait();
}
} finally {
//成功获得到了写入锁,使得等待获取写入锁的线程数量减少
readWriteLock.decrementWaitingWriters();
}
// 将正在写入的线程数量增加
readWriteLock.incrementWritingWriters();
}
}
@Override
public void unlock() {
synchronized (readWriteLock.getMUTEX()){
// 减少正在写入的线程数量
readWriteLock.decrementWritingWriters();
// 将偏好修改为false,可使读锁被更快速的所得
readWriteLock.setPreferWriter(false);
// 通知唤醒其他在Mutex monitor waitset中的线程
readWriteLock.getMUTEX().notifyAll();
}
}
}
读锁
/**
* ReadLock设计为包可见
*/
public class ReadLock implements Lock {
private final ReadWriteLockImpl readWriteLock;
ReadLock(ReadWriteLockImpl readWriteLock) {
this.readWriteLock = readWriteLock;
}
@Override
public void lock() {
// 使用MUTEX作为锁
synchronized (readWriteLock.getMUTEX()) {
//若此时有线程正在进行写操作,或者有写线程在等待且偏向写锁的标志位为true时,就无法获得读锁,被挂起
while (readWriteLock.getWritingWriters() > 0 || (readWriteLock.isPreferWriter() && readWriteLock.getWritingWriters() > 0)) {
try {
readWriteLock.getMUTEX().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 成功获得读锁,并且使readingReaders的数量增加
readWriteLock.incrementReadingReaders();
}
}
@Override
public void unlock() {
// 使用MUTEX作为锁
synchronized (readWriteLock.getMUTEX()) {
// readingReaders的数量减少
readWriteLock.decrementReadingReaders();
// 将preferWriter设置为true,可使Writer线程获得更多的机会
readWriteLock.setPreferWriter(true);
// 通知唤醒与Mutex关联monitor waitset中的线程
readWriteLock.getMUTEX().notifyAll();
}
}
}
读写锁的使用
DataShare涉及了对数据的读写操作,因此需要进行线程同步控制。
public class ShareData {
/**
* 定义共享数据(资源)
*/
private final List<Character> container = new ArrayList<>();
/**
* 构造ReadWriteLock
*/
private final ReadWriteLock readWriteLock = ReadWriteLock.readWriteLock();
/**
* 创建读取锁
*/
private final Lock readLock = readWriteLock.readLock();
/**
* 创建写入锁
*/
private final Lock writeLock = readWriteLock.writeLock();
private final int length;
public ShareData(int length) {
this.length = length;
for (int i = 0; i < length; i++) {
container.add(i,'c');
}
}
public char[] read() throws InterruptedException{
try{
// 使用读锁进行read
readLock.lock();
char[] newBuffer = new char[length];
for (int i=0;i<length;i++){
newBuffer[i] = container.get(i);
}
slowly();
return newBuffer;
}finally {
// 操作结束,将锁释放
readLock.unlock();
}
}
public void write(int index,char c) throws InterruptedException{
try{
// 使用写锁进行lock
writeLock.lock();
this.container.set(index, c);
slowly();
}finally {
// 操作完成后,释放写锁
writeLock.unlock();
}
}
private void slowly(){
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
创建2个写线程,10个读线程。
public class ReadWriteLockTest {
//This is the example for read write lock
private final static String text = "Thisistheexampleforreadwritelock";
public static void main(String[] args) {
// 定义共享数据
final ShareData shareData = new ShareData(50);
// 创建两个线程进行写操作
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run(){
for (int index = 0;index<text.length();index++){
try{
TimeUnit.SECONDS.sleep(5);
char c = text.charAt(index);
shareData.write(index,c);
System.out.println(currentThread() + " write "+c);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}.start();
}
// 创建4个线程进行写操作
for (int i=0;i<10;i++){
new Thread(){
@Override
public void run(){
while (true){
try{
TimeUnit.SECONDS.sleep(1);
System.out.println(currentThread()+" read "+new String(shareData.read()));
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}.start();
}
}
}
总结
在读操作较多的情况下,读写锁的性能要优于synchronized关键字。
Reference
《Java高并发编程详解》