java多线程设计模式之Read-Write Lock模式

本文介绍了一种利用读写锁提高程序并发性能的设计模式。该模式允许多个读取线程同时访问共享资源,但写入操作时禁止读取。通过自定义逻辑锁或使用Java并发库,有效提升了读密集型应用的效率。

    当线程“读取”实例的状态时,实例的状态不会发生变换,实例的状态尽在执行线程的时候“写入”操作时才会发生变化,是实例的状态来看,起有着本质的区别。在将要说的设计模式中,读取和写入是相互分开的,在进行相应的操作之前必须获取其对应的锁。所以多个线程可以同时读取,但是当在写入的时候不允许读取操作,一般来说,执行互斥处理会降低程序的性能,但是如果吧针对写入的互斥处理和针对读取的互斥处理分开来考虑,则可以提高程序的性能。

    结合下面的示例程序来体会一下:



首先设计一下数据类,Data类:

public class Data {
	private final char[] buffer;
	private final ReadWriteLock readWriteBlock = new ReadWriteLock();
	
	public Data(int size){
		this.buffer = new char[size];
		for(int i=0;i<buffer.length;i++){
			buffer[i]='*';
		}
	}
	
	public char[] read() throws InterruptedException{
		readWriteBlock.readLock();
		try{ 
			return doRead();
		}finally{
			readWriteBlock.readUnlock();
		}
	}
	
	public void write(char c) throws InterruptedException{
		readWriteBlock.writeLock();
		try{
			doWrite(c);
		}finally{
			readWriteBlock.writeUnlock();
		}
	}
	
	private char[] doRead(){
		char[] newBuf = new char[buffer.length];
		for(int i=0;i<buffer.length;i++){
			newBuf[i]=buffer[i];
		}
		slowly();
		return newBuf;
	}
	
	private void doWrite(char c){
		for(int i=0;i<buffer.length;i++){
			buffer[i]=c;
			slowly();
		}
	}
	
	private void slowly(){
		
		try{
			Thread.sleep(50);
		}catch(InterruptedException e){
			e.printStackTrace();
		}		
	}
}
按照上面的设计,则需要在read()方法和write()方法中使用before/After模式来进行逻辑设计,同时需要在已进入方法的时候就获取相对应的锁,当方法正常结束或者出现异常而结束的时候必须释放锁,所以设计了ReadLockLock类的readLock和writeLock,并且对于方法中需要的互斥处理则在ReadWriteLock类中实现。ReadWriteLock类设计如下:

/*
 * 设置读写锁的操作设置
 * 1.首先考虑读与写的互斥操作
 * 2.考虑写同写之间的互斥操作
 * 3.使用多种模式结合,singletonThreadException
 * 和immutable模式,以及GuardSuspension模式
 */
public final class ReadWriteLock {
	private int readingReaders = 0;
	private int waitingWriters = 0;
	private int writingWriters = 0;
	private boolean preferWriter = true;
	
	public synchronized void readLock() throws InterruptedException{
		while (writingWriters > 0|| (preferWriter && waitingWriters>0)){
			wait();
		}
		readingReaders++; 
		
	}
	public synchronized void readUnlock(){
		readingReaders--;
		preferWriter = true;
		notifyAll();
	}
	
	public synchronized void writeLock() throws InterruptedException{
		waitingWriters++;
		try{
			while(readingReaders > 0|| writingWriters > 0){
				wait();
			}
		}finally{
			waitingWriters--;
		}
		
		writingWriters++;
	}
	
	public synchronized void writeUnlock(){
		writingWriters--;
		preferWriter = false;
		notifyAll();
	}
}
可以发现,相应的互斥处理都在此类中进行了实现,在Data类中就不再需要使用synchronized来修饰对应的方法了。此类解决了开头所描述的问题,同时为了提高程序的生存性,我们定义了一个字段,preferWriter,来限定表示写入优先。

    设计下面的ReadThread类:

public class ReadThread extends Thread {
	private final Data data;
	public ReadThread(Data data){
		this.data = data;
	}
	
	public void run(){
		try{
			while(true){
				char[] readBuf = data.read();
				System.out.println(Thread.currentThread().getName()+" reads "+String.valueOf(readBuf));
			}			
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

设计WriteThread类:

public class WriterThread extends Thread {
	private static final Random random = new Random();
	private final Data data;
	private final String filler;
	private int index = 0;
	
	public WriterThread(Data data,String filler){
		this.data = data;
		this.filler = filler;
	}
	
	public void run(){
		try{
			while(true){
				char c = nextChar();
				data.write(c);
				Thread.sleep(random.nextInt(3000));
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
	
	private char nextChar(){
		char c = filler.charAt(index);
		index++;
		if(index >= filler.length()){
			index = 0;
		}
		return c;
	}
}
最后通过一个main函数启动看一下:

public class ReadWriteLockMain {

	public static void main(String[] args) {
		Data2 data = new Data2(10);
		new ReadThread(data).start();
		new ReadThread(data).start();
		new ReadThread(data).start();
		new ReadThread(data).start();
		new ReadThread(data).start();
		new ReadThread(data).start();
		new WriterThread(data,"ABCDEFGHIJKLMNOPQRSTUVWXYZ").start();
		new WriterThread(data,"abcdefghijklmnopqrstuvwxyz").start();

	}

}
执行结果如下:


     此模式的要点:

    这个模式利用了读取线程之间不会冲突的特性,由于读取不会改变共享资源的状态,所以彼此之间可以不必进行互斥处理,多个reader可以同时进行工作,故提高了程序的性能。但是呢,性能的提高并不是绝对的,它也是仅适用于某些情况下的:

   1适合读取任务繁重的操作,如果利用第一节所说的singleThreadExecution模式即便是读取操作也会是每次只能运行一个线程,如果read的内容比较大,很耗费试时间时,使用本节课所讲的模式是一个不错的选择。

2.适合读取比写入频率高的情况,如果写入的频率较小,那么可以很好的体现此模式的优点,从而能够让多个读取的线程同时读取,如果写入的频率较高的化,读取的线程则都处于等待状态,此模式的优势也就无法体现了。

在这里还要讨论一下锁的含义:

Java中的每个实例都持有一个锁,但是一个锁不可以由两个以上的线程同时获取,这种结构是java编程规范决定的,java的虚拟机也是这么实现的,这是java从一开始就提供的所谓的“物理锁”,程序并不能改变这种锁的运行。

而示例中提到的“用于读取的锁”和“用于写入的锁”所指的锁与使用synchronized获取的锁是不一样的,这是开发人员自己实现的一种结构,并不是java编程规范所要求的,这就是所谓的“逻辑锁”。开发人员可以修改改变锁的运行。


借助java.util.concurrent.locks包重建Data类

public class Data {
	private final char[] buffer;
	private final ReadWriteLock lock = new ReentrantReadWriteLock(true);
	private final Lock readLock = lock.readLock();
	private final Lock writeLock = lock.writeLock();
	
	public Data(int size){
		this.buffer = new char[size];
		for(int i=0;i<buffer.length;i++){
			buffer[i]='*';
			
		}
	}
	
	public char[] read() throws InterruptedException{
		readLock.lock();
		try{
			return doRead();
		}finally{
			readLock.unlock();
		}
	}
	
	public void write(char c) throws InterruptedException{
		writeLock.lock();
		try{
			doWrite(c);
		}finally{
			writeLock.unlock();
		}
	}
	
	private char[] doRead(){
		char[] newBuf = new char[buffer.length];
		for(int i=0;i<buffer.length;i++){
			newBuf[i]=buffer[i];
		}
		slowly();
		return newBuf;
	}
	
	private void doWrite(char c){
		for(int i=0;i<buffer.length;i++){
			buffer[i]=c;
			slowly();
		}
	}
	
	private void slowly(){
		try{
			Thread.sleep(50);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
	
}
运行结果同原结果一致,但是代码的书写量少了很多,而且不必再建ReadWriteLock类,直接使用java.util.concurrent.locks.ReadWriteLock类即可。同时,值得注意的是,在实现的时候必须考虑共享资源的安全性,充分的考虑互斥性,不要忘记释放锁获取的实例锁。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值