Read-Write Lock 模式
当线程“读取”实例的状态时,示例的状态不会发生变化。示例的状态尽在线程执行“写入”操作时才会发生变化。从实例的状态变化这个观点来看,“读取”和“写入”有着本质的区别。
在Read-Write Lock模式中,读取操作和写入操作是分开考虑的,在执行读取操作之前,线程必须获取用于读取的锁。而在执行写入操作之前,线程必须获取用于写入的锁。
由于当线程执行读取操作时,示例的状态不会发生变化,所以多个线程可以同时读取。但在读取时,不可写入。
当线程执行写入操作时,示例的状态就会发生变化。因此,当有一个线程正在写入时,其他线程不可读取或写入。
一般来说,执行互斥处理会降低程序性能。但如果把针对写入的互斥处理和针对读取的互斥处理分开来考虑,则可以提高程序性能。
示例程序
类的一览表
名字 | 说明 |
---|---|
WriterThread | 表示写入线程的类 |
ReadThread | 表示读取线程的类 |
Data | 可以读写的类 |
Main | 测试程序行为的类 |
ReadWriteLock | 提供读写锁的类 |
Main类
Main类首先会创建一个Data类的示例。然后创建对该Data类实例执行读取操作的线程实例,以及执行写入操作的线程实例,并启动它们。
这里启动了六个读取线程和两个写入线程。
Main类
1 | public class Main { |
Data类
Data类是可以执行读取和写入操作的类。
buffer字段是实际的读写对象char的数组。
lock字段保存的是该模式的主角ReadWriteLock实例。
构造函数会根据参数传入的长度来分配一个char数组,并初始化buffer字段,同时以字符“”填满buffer,此处的“”为初始值。
read方法执行读取操作。实际的读取操作是通过doRead方法执行的,而doRead方法夹在lock.readLock和lock.readUnlock之间。lock.readLock表示获取用于读取的锁,lock.readUnlock则表示释放用于读取的锁。
夹住doRead的地方使用了try…finally结构。这是为了确保在执行了readLock之后,不管doRead中发生了什么情况,lock.readUnlock都一定会被调用。
doRead方法用于执行实际的读取操作。该方法会创建一个新的char数组,来赋值buffer字段的内容,并返回newbuf。
doWrite方法用于执行实际的写入操作。该方法会以参数传入的字符c来填满buffer字段。
slowly方法用于辅助模拟耗时的操作,次数是sleep约50毫秒。
Data 类
1 | public class Data { |
WriteThread类
WriterThread类表示的是对Data实例执行写入操作的线程。构造函数的参数filter是一个字符串,程序会逐个去除该字符串中的字符,并write到data的实例中。
WriteThread类
1 | import java.util.Random; |
ReadThread类
ReadThread类
1 | public class ReaderThread extends Thread { |
ReadWriteLock类
该类提供了用于读取的锁和用于写入的锁。
为了确保安全性,我们必须放置如下两种冲突。
- “读取”和“写入”冲突
- “写入”和“写入”冲突
由于不存在“读取”和“读取”冲突,所以我们无需对其进行考虑。
为了防止发生冲突,需要考虑下面四种情况:
当线程想要获取用于读取的锁时……
- 如果有线程正在执行写入,则等待。
- 如果有线程正在执行读取,则无需等待
当线程想要获取想要写入的锁时…… - 如果有线程正在执行写入,则等待
- 如果有线程正在执行读取,则等待
1 | public class ReadWriteLock { |