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

6. Read-Write Lock模式

大家一起读没问题,但读的时候不要写哦

 

Read-Write Lock模式中,读取操作和写入操作是分开考虑的,在执行读取操作之前,线程必须获取用于读取的锁,而在执行写入操作之前,线程必须获取用于写入的锁。

由于当线程执行读取操作时,实例的状态不会发生变化,所以多个线程可以同时读取,但在读取的时候,不可以写入。

当线程执行写入操作时,实例的状态就会发生变化。因此,当有一个线程正在写入时,其他线程不可以读取或写入。

 

 

 

实例图:

 

Main类:

先创建一个Data类的实例,然后创建对该data实例执行读取操作的线程,以及执行写入操作的线程,并启动他们。

public class Main {

public static void main(String[] args) {

Data data = new Data(10);

new ReaderThread(data).start();

new ReaderThread(data).start();

new ReaderThread(data).start();

new ReaderThread(data).start();

new ReaderThread(data).start();

new WriteThread(data,"ABCDEFGHIJKLMNOPQRSTUVWXYZ").start();

new WriteThread(data,"abcdefghijklmnopqrstuvwxyz").start();

}

}

 

Data类:

buffer字段是实际的读写对象char的数组。

lock字段保存ReadWriteLock实例。

read方法执行读取操作,实际读取操作是doRead()执行的,而doRead方法夹在lock.readLock()和lock.readUnlock()之间,

lock.readLock()获取用于读取的锁,lock.readUnlock()释放用于读取的锁

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 char[] read() throws InterruptedException{

lock.readLock();

try {

return doRead();

}finally{

lock.readUnlock();

}

}

public void write(char c){

lock.writeLock();

try{

doWrite(c);

}finally{

lock.writeUnLock();

}

}

public void  doWrite(char c){

for(int i =0;i<buffer.length;i++){

buffer[i] = c;

slowly();

}

}

private char[] doRead(){

char[] newbuf = new char[buffer.length];

for(int i=0;i<buffer.length;i++){

newbuf[i] = buffer[i];

}

slowly();

return newbuf;

}

public void slowly(){

try {

Thread.sleep(50);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

 

WriteThread类:

WriteThread类表示的是对Data实例执行写入操作的线程。构造函数的参数filter是一个字符串,程序会逐个取出该字符串的内容,并Write到data实例中,nextChar()用于获取下一个字符。

public class WriteThread extends Thread{

private static final Random random = new Random();

private final Data data;

private final String filter;

private int index = 0;

public WriteThread(Data data,String filter){

this.data =data;

this.filter = filter;

}

public void run(){

try {

while(true){

char c = nextchar();

data.write(c);

Thread.sleep(random.nextInt(3000));

}

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

public char nextchar(){

char c = filter.charAt(index);

index++;

if(index>=filter.length()){

index = 0;

}

return c;

}

}

 

ReaderThread类

ReaderThread类表示的是执行读取操作的线程。该类会循环调用data.read(),并且显示读取到的char数组,String.valueOf()是String类的静态方法,用于将参数转换为字符串。

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) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

ReadWriteLock类

该类提供了用于读取的锁和用于写入的锁。

read读取和写入的冲突

写入和写入的冲突

四种情况分析:

当线程想要获取用于读取的锁时

如果有线程正在执行写入,则等待

如果有线程正在读取,无需等待。

当线程想要获取用于写入的锁时

如果有线程正在执行写入则等待

如果有线程正在读取,则等待(避免引起read-write conflict)

 

readingReaders在readLock方法的最后执行递增,readUnlock的开头执行递减操作。readingReaders表示当前实际正在读取的线程个数(在readLock和readUnlock之间)

waitingWriters在writeLock方法的开头执行递增,在随后的finally进行递减操作。

writingWriters在writeLock的最后执行递增,在writeUnlock的前头执行递减操作。表示正在写入中的线程个数。

public class ReadWriteLock {

//实际正在读取中的线程个数

private int readingReaders = 0;

//正在等待写入的线程个数

private int waitingWriters = 0;

//实际正在写入中的线程个数

private int writingWriters = 0;

//若写入优先,则为true

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(){

waitingWriters++;

try {

while(readingReaders>0||writingWriters>0){

wait();

}

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} finally{

waitingWriters--;//正在等待写入的线程个数加1

}

writingWriters++;

}

public synchronized void writeUnLock(){

writingWriters--;

perferWriter = false;

notifyAll();

}

}

readLock方法和writeLock都使用了守护条件(Guarded Suspension模式)

readLock方法守护条件是:

没有线程正在执行写入操作,writingWriters<=0

writeLock方法:

守护条件:没有线程正在执行写入或读取操作

readingReaders<=0 && writingWriters <=0

 

拓展:java.util.concurrent.locks.ReentrantReadWriteLock

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){

writeLock.lock();

try{

doWrite(c);

}finally{

writeLock.unlock();

}

}

public void  doWrite(char c){

for(int i =0;i<buffer.length;i++){

buffer[i] = c;

slowly();

}

}

private char[] doRead(){

char[] newbuf = new char[buffer.length];

for(int i=0;i<buffer.length;i++){

newbuf[i] = buffer[i];

}

slowly();

return newbuf;

}

public void slowly(){

try {

Thread.sleep(50);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

ReentrantReadWriteLock主要特征:

公平性

可重入性

锁降级

 

总结

SharedResource角色提供了read和write两个操作。read不会改变其状态,而write会,

Reader角色在read时,Writer角色必须等,而当writer角色正在write时,Reader角色和其他Writer角色也必须等待。于是引入了ReadWriteLock角色,该角色提供分别用于read和write的锁,来执行上述的互斥处理,这样,确保了SharedResource角色的安全性,当read操作特别繁重时,程序性能能大大提高,在实现时,必须充分考虑执行互斥处理时采用的Guarded Suspension模式的守护条件。使用finally防止忘记释放锁,这就是Read Write Lock模式。

 

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页