首先我们来看一个read-write lock pattern的范例程序,该程序是一个多线程程序,用于对Data类的实例进行读取或者写入操作。
该程序需要实现的是当线程读取时,不允许写入线程更改实例的状态,但是此时允许新的线程读取实例的状态。而当写入线程工作时,不允许读线程读取实例的状态。
Main | 操作测试用的类 |
Data | 可读写的类 |
WriterThread | 写入的类 |
ReadThread | 读取操作的类 |
ReadWriteLock | 提供读写锁定的类 |
其类图如下所示
Data类的代码如下所示:
package cn.com.chanjet.readwritepattern; /** * Created by hujun on 2014/7/16. */ public class Data { private final char[] buffer; private final ReadWriteLock lock = new ReadWriteLock(); public Data(int size) { this.buffer = new char[size]; for (int i = 0; i < buffer.length; i++) { buffer[i] = '*'; } } public void write(char c) throws InterruptedException { // 获取锁定 lock.writeLock(); try { doWrite(c); } finally { // 解除锁定 lock.writeUnlock(); } } public char[] read() throws InterruptedException{ // 获取锁定 lock.readLock(); try{ return doRead(); }finally { // 解除锁定 lock.readUnlock(); } } 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(); } } } |
WriterThread类
package cn.com.chanjet.readwritepattern; import java.util.Random; /** * Created by hujun on 2014/7/16. */ 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){ System.out.println(e); } } private char nextchar() { char c=filler.charAt(index); index++; if(index >= filler.length()){ index=0; } return c; } } |
ReaderThread类
package cn.com.chanjet.readwritepattern; /** * Created by hujun on 2014/7/16. */ public class ReaderThread extends Thread{ private final Data data; public ReaderThread(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){ System.out.println(e); } } } |
ReadWriteLock类
package cn.com.chanjet.readwritepattern; /** * Created by hujun on 2014/7/16. */ public class ReadWriteLock { private int readingReaders=0; //实例正在读取的线程数量 private int waitingWriters=0; //调用writeLock时进入wait状态的线程数量 private int writingWriters=0; //正在写入操作的线程数量,这个值不是0就是1 private boolean perferWriter=true; //用于标识写入优先还是读取优先 public synchronized void readLock() throws InterruptedException{ //如果有写入进程正在工作,或者写入优先级高,此时有等待写入的进程工作时,读进程进入等待队列 while (writingWriters>0||(perferWriter&&waitingWriters>0)){ wait(); } readingReaders++; } public synchronized void readUnlock() { readingReaders--; perferWriter=true; notifyAll(); } // 当有进程正在读取数据时,写进程阻塞 public synchronized void writeLock() throws InterruptedException{ waitingWriters++; try { while (readingReaders>0||writingWriters>0){ wait(); } }finally { waitingWriters--; } writingWriters++; } public synchronized void writeUnlock() { writingWriters--; perferWriter=false; notifyAll(); } } |
该线程需要防止下面的两种冲突:
- “读取”与“写入”的冲突
- “写入”与"写入"的冲突
- 当线程想要获取读访问锁定时:
-
- 当有线程正在写入时,等待
- 已经有线程正在读取时,不等待
- 当线程想要获取写入用的锁定时:
- 已经有进程在写入时,等待
- 已经有进程正在读取时,等待
检验警戒条件:
readLock方法
线程进行实际的读取操作之前会调用readLock方法,其警戒条件为:没有线程正在执行写入的操作,即writingWriter <=0,因此while语句的条件为警戒条件的否定,while(writingWriter>0).
readLock方法退出之前其线程数据量readingReaders递增。
writeLock方法:
线程进行写入操作前,会调用writeLock方法,其警戒条件为:没有线程正在执行读取或写入操作,即readingReaders<=0&&writingWriters>0,因此while语句的条件为警戒条件的否定,while(readingReaders>0||writingWriters>0)
writeLock方法退出之前,需要将实际写入的线程数量writingWriters递增。
小结
在编写多线程程序的时候,最主要的是需要明确
1:多线程程序的参与者有哪些,这些参与者在多线程程序中扮演什么角色
2:多线程的共享资源是什么,其余各个参与者之间时什么关系
3:根据以上两点确定需要多线程程序中需要保护的到底是什么,在保护的过程如何避免数据不一致以及死锁情况的发生。
4:在确保程序正确的情况下,提高多线程程序的性能,需要注意的是此时的任何修改都应该是慎重的,因为此时对代码的修改会导致数据冲突情况的发生,有时宁愿牺牲一部分性能,也不要对多线程程序进行修改。
注:此文是对《Java多线程设计模式》中read-writer Lock Pattern的一个小结。大家如果对这部分内容感兴趣也可以直接阅读《Java多线程设计模式》这本书。