1 场景引入
在多线程的情况下访问共享资源,需要对资源进行同步操作以防止数据不一致的情况发生,通常我们可以使用synchronized关键字或者显式锁。
对资源的访问一般包括两种类型的动作——读和写(更新、删除、增加等资源会发生变化的动作),如果多个线程在某个时刻都在进行资源的读操作,虽然有资源的竞争,但是这种竞争不足以引起数据不一致的情况发生,那么这个时候直接采用排他的方式加锁,就显得有些简单粗暴了。
**其实多线程在同一时间都进行读操作时不会引起冲突之外,其余的情况都会导致访问的冲突,需要对资源进行同步处理。**比如说两个线程同时对数据读取,是不会引起冲突,但是如果一个都在写或者一个读取,一个写入就会引起冲突。
如果对某个资源读的操作明显多过于写的操作,那么多线程读时并不加锁,很明显对程序性能的提升会有很大的帮助
所以多线程下,如果读写分离,则可以提高效率,读数据是的是可以并行的,只有写的时候加锁串行
我们更多需求更多的时候就是读取的时候,允许多个线程去读取,但是在写如数据的时候,只能有一个线程去写入,其他线程不能进行读取和写入
2 简单实现
简单实现,简单了解一下读写锁的设计原理
根据上面的分析:
- 多个线程读取数据是可以并行的,也就是说允许多个线程读取数据
- 写数据的时候,只能允许一个线程在写数据,其他线程不能读也不能写
所以定义锁的时候,设计了一下几个变量:
/**
* @Description: 当前正在读取数据线程数量
*/
private int readingThreads = 0;
/**
* @Description: 等待去读取数据的线程的数量, 但是读取不了,放到了wait队列中
*/
private int waitReadThreads = 0;
/**
* @Description: 正在写入数据的线程数量 只有一个
*/
private int writingThreads = 0;
/**
* @Description: 记录当前有多少个线程想要写入,但是写入不了,放到了wait队列中,在等待其他释放锁
*/
private int waitWriteThreads = 0;
public class ReadWriteLock {
/**
* @Description: 当前正在读取数据线程数量
*/
private int readingThreads = 0;
/**
* @Description: 等待去读取数据的线程的数量, 但是读取不了,放到了wait队列中
*/
private int waitReadThreads = 0;
/**
* @Description: 正在写入数据的线程数量 只能有一个
*/
private int writingThreads = 0;
/**
* @Description: 记录当前有多少个线程想要写入,但是写入不了,放到了wait队列中,在等待其他释放锁
*/
private int waitWriteThreads = 0;
/**
* 加读锁
*/
public synchronized void readLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitReadThreads可能需要加1的
this.waitReadThreads++;
try {
while (this.writingThreads > 0){
// 此时有线程正在写数据,便需要等待不能进行读取数据
// 不能在++,需要立刻等待,就放到外面,但是可能会记录的数量不对
// this.waitReadThreads++;
this.wait();
}
// 当前没有线程在写入数据,那么就可以读取数据
this.readingThreads++; // 正在读取数据的线程数量++
}finally {
// 当释放掉锁的时候,waitReadThreads --
this.waitReadThreads--;
}
}
/**
* 释放掉读锁
*/
public synchronized void unReadLock(){
// 当前正在读取数据线程数量 --
this.readingThreads--;
// 唤醒其他线程
this.notifyAll();
}
/**
* 加写锁
*/
public synchronized void writeLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitWriteThreads可能需要加1的
this.waitWriteThreads++;
try {
while (this.writingThreads > 0 || this.readingThreads > 0){
// 当有其他线程正在读取数据和写入数据的时候,此时不能写入数据,需要等待
// this.waitWriteThreads++; // 同样也不能再这样++
this.wait();
}
// 当前其他线程正在读取数据和写入数据的时候, 就可以写入数据了
this.writingThreads++; // 正在写入数据的线程数量++
}finally {
// 当释放掉锁的时候,waitWriteThreads --
this.waitWriteThreads--;
}
}
/**
* 释放写锁
*/
public synchronized void unWriteLock(){
this.writingThreads--;
this.notifyAll();
}
}
测试:
- 定义一个共享数据,里面使用了定义的锁,并定义了读取数据和写入数据的操作
public class ShareData {
/**
* 定义一个buffer,从里面读取数据和写入数据
*/
private final char[] buffer;
/**
* 定义一个锁
*/
private final ReadWriteLock lock = new ReadWriteLock();
public ShareData(int size) {
this.buffer = new char[size];
// 初始化数据
for (int i = 0; i < size; i++) {
this.buffer[i] = '*';
}
}
public char[] read() throws InterruptedException {
try {
// 1 加读锁
lock.readLock();
// 2 读取数据
return doRead();
}finally {
// 释放锁
lock.unReadLock();
}
}
private char[] doRead() {
char[] temp = new char[buffer.length];
for (int i =0; i<buffer.length;i++){
temp[i] = buffer[i];
}
// 读取结束之后,休眠一会
sleep(50);
return temp;
}
private void sleep(int mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void write(char c) throws InterruptedException {
try {
// 1 加写锁
lock.writeLock();
// 2 写数据
doWrite(c);
}finally {
// 释放锁
lock.unWriteLock();
}
}
private void doWrite(char c) {
for (int i =0; i<buffer.length;i++){
buffer[i] = c;
sleep(10);
}
}
}
- 定义一个线程读取数据
public class ReadThread extends Thread{
private final ShareData shareData;
public ReadThread(ShareData shareData) {
this.shareData = shareData;
}
@Override
public void run() {
try{
while (true){
char[] data = shareData.read();
System.out.println(Thread.currentThread().getName() + " read " + String.valueOf(data));
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 定义一个线程写数据
public class WriterThread extends Thread{
private static final Random random = new Random(System.currentTimeMillis());
private final ShareData shareData;
// 写入内容
private final String content;
private int index = 0;
public WriterThread(ShareData shareData, String content) {
this.shareData = shareData;
this.content = content;
}
@Override
public void run() {
// 在这里,如果中断,就会进入到catch,不用自己break,
// 因为run方法已经结束了
try{
while (true){
char c = nextChar();
shareData.write(c);
System.out.println(Thread.currentThread().getName() + " write " + String.valueOf(c));
Thread.sleep(random.nextInt(1000));
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
/**
* 获取content中的下一个字符
* @return
*/
private char nextChar(){
char c = content.charAt(index);
index++;
if(index>=content.length()){
// 如果index大于content的长度,就重置一下
index = 0;
}
return c;
}
}
- 测试类
public class ReadWriteLockTest {
public static void main(String[] args) {
final ShareData shareData = new ShareData(10);
new ReadThread(shareData).start();
new ReadThread(shareData).start();
new ReadThread(shareData).start();
new ReadThread(shareData).start();
new WriterThread(shareData,"qwertyuiop").start();
new WriterThread(shareData,"QWERTYUIOP").start();
}
}
运行结果发现,大部分是读取数据的线程抢到执行权,这也很正常,我们声明了4个读取数据的线程
3 改进:支持设置偏好
package study.wyy.concurrency.readWriteLock;
public class ReadWriteLock {
/**
* @Description: 当前正在读取数据线程数量
*/
private int readingThreads = 0;
/**
* @Description: 等待去读取数据的线程的数量, 但是读取不了,放到了wait队列中
*/
private int waitReadThreads = 0;
/**
* @Description: 正在写入数据的线程数量 只有一个
*/
private int writingThreads = 0;
/**
* @Description: 记录当前有多少个线程想要写入,但是写入不了,放到了wait队列中,在等待其他释放锁
*/
private int waitWriteThreads = 0;
/**
* @Description: 是否写入优先级更高
*/
private boolean preferWrite;
public ReadWriteLock(boolean preferWrite) {
this.preferWrite = preferWrite;
}
public ReadWriteLock() {
// 默认 true
this(true);
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 加读锁
*/
public synchronized void readLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitReadThreads可能需要加1的
this.waitReadThreads++;
try {
while (writingThreads > 0 || (preferWrite && this.waitWriteThreads>0)) {
// 当前有线程正在进行写入的时候,肯定是不能这个读取数据的线程肯定是不能进行读取数据的,进入等待
// 如果更偏爱写入,并且有写入的线程数量大于0,也让读取的线程进入等待
// this.waitReadThreads++; 这个放在这里不合适,需要立马进入等待,就放到外面,但是可能会记录的数量不对
this.wait();
}
// 此时没有线程进行写入数据,就可以读取数据了, 正在读取数据的线程数加1
this.readingThreads++;
} finally {
// 最终释放锁的时候,要减减waitingReaders
this.waitReadThreads--;
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 释放读锁
*/
public synchronized void readUnLock() throws InterruptedException {
// 1 读取数据的线程的数量减1
this.readingThreads--;
// 2 释放
this.notifyAll();
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 加写锁
*/
public synchronized void writeLock() throws InterruptedException {
// 获取锁的时候,有可能获取不到锁,进入到等待队列(waitSet)中, 所以waitWriteThreads可能需要加1的
this.waitWriteThreads++;
try {
while (readingThreads > 0 || writingThreads > 0){
// 当有其他线程在进行读取或者写入的时候,则当前线程不能进行写操作,只能等待
this.wait();
}
// 没有其他线程在进行读取或者写入的时候,那就可以进行写入
this.writingThreads++;
} finally {
// 最终释放锁的时候,要减减waitWriteThreads
this.waitWriteThreads--;
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/6 6:10 下午
* @Description: 释放写锁
*/
public synchronized void unWriteLock(){
this.writingThreads--;
this.notifyAll();
}
}