多线程设计模式: 读写锁分离设计模式

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();
    }

}

测试:

  1. 定义一个共享数据,里面使用了定义的锁,并定义了读取数据和写入数据的操作
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);
        }
    }
}
  1. 定义一个线程读取数据
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();
        }
    }

}
  1. 定义一个线程写数据
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;
    }
}

  1. 测试类
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();
    }


}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值