概述
什么是Read-Write Lock Pattern?
设想一个场景,老师在黑板上写字,全班在下面一起看,老师想要擦除,但是有人表示自己还没看完,于是老师等待大家看完才擦除黑板。
对于这样的情况就是Read-Write Pattern,线程读取实例状态的时候,实例的状态不会改变,而会使状态变化的只有对线程的写入。
这个模式就是将读和写区分开来进行处理,读之前需要获取读的锁,写的时候需要获取写的锁。读取的时候有多个线程同时读取没有关系,但是有人读取的时候不能进行写操作!
一般来说,进行共享互斥会导致程序性能变差,但是我们将写入的共享互斥和读取的共享互斥分开考虑就会提高程序的性能。
首先直接看代码,设想一个场景,6个线程写数据,2个线程读数据,要保证数据都是一个写者写的,不会夹杂着,我们用线程提供一个字符占满所有的数据区域,使得这一现象更直观。
首先看ReadWriteLock类:
package readWriteLock;
public class ReadWriteLock {
private int readingReaders = 0;//实际正在读取的线程数量
private int waitingWriters = 0;//正在等待写入的线程数量
private int writingWriters = 0;//实际正在写入的线程数量
private boolean preferWriter = true;//是否优先写入
public synchronized void readLock() throws InterruptedException{
//当正在写的线程小于等于0并且(不优先写或者等着写的线程数量小于等于0)才可以进入临界条件
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--;//正在等待写入的线程数量减1
}
writingWriters++;//实际正在写入的线程数量加1
}
public synchronized void writerUnlock(){
writingWriters--;//正在写入的数量减一
preferWriter = false;//不优先读
notifyAll();
}
}
然后是Data类:
package readWriteLock;
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();//这个finally哪怕是在try语句中return了也要执行
}
}
public void write(char c) throws InterruptedException {
lock.writeLock();
try{
doWrite(c);
}finally{
lock.writerUnlock();
}
}
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) {
}
}
}
实现ReaderThread类:
package readWriteLock;
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) {
}
}
}
实现WriterThread类:
package readWriteLock;
import java.util.Random;
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);
System.out.println(Thread.currentThread().getName()+"写入"+c);
Thread.sleep(random.nextInt(3000));
}
} catch (InterruptedException e) {
}
}
private char nextchar() {
char c = filler.charAt(index);
index++;
if (index >= filler.length()) {
index = 0;
}
return c;
}
}
最后写一个测试类Test来进行测试:
package readWriteLock;
public class Test {
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 ReaderThread(data).start();
new WriterThread(data, "ABCDEFGHIJKLMNOPQRSTUVWXYZ").start();
new WriterThread(data, "abcdefghijklmnopqrstuvwxyz").start();
}
}
好了,现在我们回头看看代码,对于read-write lock我们应该防止下面的冲突:
1、读取和写入的冲突
2、写入和写入的冲突
整个模式的关键在于ReadWriteLock中关于读锁/写锁的lock和unlock,关于这个模式主要结构如下:
前置处理(获取锁定)
try{
实际操作
}
finally{
后续处理(解除锁定)
}//finally中的代码块哪怕在try中执行了return也会执行
除了ReadWriteLock中存在互斥共享相关代码,其他地方不存在。
获取锁定则是guarded suspension pattern中的知识点,在本模式中,lock.readLock()和lock.readUnlock()之间允许读不允许写,lock.writeLock()和lock.writeUnlock()之间读写都不可以。
Read-Write Lock Pattern的所有参与者:
1、Reader(读取者)参与者
对sharedResource参与者进行read
2、Writer(写入者)参与者
对sharedResource参与者进行write
3、SharedResource(共享资源)参与者
代表Reader和Writer参与者所共享的资源,提供不会改变内部状态的操作read和会改变内部状态的操作write
4、ReadWriteLock(读写锁)参与者
提供了读操作分别需要的加锁和解锁
当read的时候不能写,但是允许其他线程进行read
当write的时候不允许其他线程读,也不允许其他线程写
什么时候用这种模式?
1、读取操作繁重的时候
当我们使用Single Threaded Execution Pattern的时候,连read操作一次也只有一条线程可以执行,如果read操作特别繁重(花时间),则可以使用当前模式来解决,使得程序可以同时read。
当read操作很简单(不花时间)时,使用Single Threaded Execution操作可能更好,因为当前模式比Single Threaded Execution更复杂。
2、读取次数比写入此时频繁
这个模式优点在于reader之间不会冲突,但是当write操作频率比较高的时候,Writer经常会阻挡Reader的进行,这样就没办法展示这个模式的优点。
这个模式Lock的意义
使用synchronized可以获得锁,当前模式的lock也可以获得锁,那这两个锁有什么区别呢。
对于synchronized来说,同一个实例的锁无法被两个以上线程所获取,这是Java语言规格里一开始就提供的机制,Java无法改变这种锁的行为,我们称之为物理上的锁。
相对的,这一章的锁与synchronized获取的锁意义上不同,不是Java提供的,而是自己实现的,我们称之为逻辑上的锁,可以自己写锁类更改锁的机制。
在锁类中,提供了多种逻辑锁定,但是实际上多个逻辑锁定只用到了一个物理锁定,就是这个锁类的实例的锁。
最后
以上就是优秀电灯胆为你收集整理的Read-Write Lock Pattern的全部内容,希望文章能够帮你解决Read-Write Lock Pattern所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复