读写锁分离

  • 场景描述
  • 读写锁设计
  • 接口定义
  • Lock接口
  • ReadWriteLock
  • 程序实现
  • ReadWriteLockImpl
  • 写锁
  • 读锁
  • 读写锁的使用
  • 总结
  • Reference


场景描述

在多线程的情况下访问共享资源,需要对资源进行同步操作以防止数据不一致的情况。对资源的访问一般包括两种类型的动作——读和写(更新、删除、增加等资源会发生变化的动作)。如果多个线程在某个时刻都在进行对资源的读操作,虽然有资源的竞争,但是这种竞争不足以产生数据不一致的情况发生,这个时候如果直接使用排它的方式加锁,就显得有些简单粗暴。

线程




不冲突

冲突


冲突

冲突

如果对某个资源的读操作明显多于写操作,那么多线程读时不加锁,对程序性能的提升会有很大的帮助。

读写锁设计

接口定义

Lock接口

Lock接口定义了锁的基本操作,加锁和解锁。显示锁的操作建议与try finally语句块一起使用。

public interface Lock {
    /**
     * 获取显式锁,没有获得所得线程被阻塞
     * @throws InterruptedException
     */
    void lock() throws InterruptedException;

    /**
     * 释放获取的锁
     */
    void unlock();
}

ReadWriteLock

ReadWriteLock并不是Lock,主要是用于创建read lock和write lock,提供查询功能用于查询当前有多少个reader和writer以及waiting中的writer。

public interface ReadWriteLock {
    /**
     * 创建reader锁
     */
    Lock readLock();

    /**
     * 创建write锁
     */
    Lock writeLock();

    /**
     * 获取当前有多少个线程在执行写操作
     */
    int getWritingWriters();

    /**
     * 获取当前有多少个线程在等待获取写入锁
     */
    int getWaitingWriters();

    /**
     * 获取当前有多少个线程在等待获取reader锁
     */
    int getReadingReaders();

    /**
     * 工厂方法,创建ReadWriteLock
     */
    static ReadWriteLock readWriteLock(){
        return new ReadWriteLockImpl();
    }

    /**
     * 工厂方法,创建ReadWriteLock,并设置preferWriter
     */
    static ReadWriteLock readWriteLock(boolean preferWriter){
        return new ReadWriteLockImpl(preferWriter);
    }
}

程序实现

ReadWriteLockImpl

ReadWriteLockImpl是一个工厂类,为了减少对外暴露的细节,将其设计为包可见的类。在实现内部,Mutex进行数据同步与线程之间的通信。preferWriter的作用在于控制倾向性,读写锁适用于读多写少的场景,如果preferWriter设置为false,那么写线程可能会发生饥饿。

/**
 * default  包可见,创建时使用ReadWriteLockcreate方法
 */
class ReadWriteLockImpl implements ReadWriteLock {
    /**
     * 定义对象锁
     */
    private final Object MUTEX = new Object();

    /**
     * 当前有多少个线程正在写入
     */
    private int writingWriters = 0;

    /**
     * 当前有多少个线程正在等待写入
     */
    private int waitingWriters = 0;

    /**
     * 当前有多少个对象正在read
     */
    private int readingReaders = 0;

    /**
     * 设置read和write的偏好
     */
    private boolean preferWriter;

    /**
     * 默认情况下preferWriter为true
     */
    public ReadWriteLockImpl() {
        this(true);
    }

    /**
     * 构建ReadWriteLockImpl,传入preferWriter
     * @param preferWriter
     */
    public ReadWriteLockImpl(boolean preferWriter) {
        this.preferWriter = preferWriter;
    }

    /**
     * 创建read lock
     */
    @Override
    public Lock readLock() {
        return new ReadLock(this);
    }

    /**
     * 创建write lock
     */
    @Override
    public Lock writeLock() {
        return new WriteLock(this);
    }

    /**
     * 使写线程的数量增加
     */
    void incrementWritingWriters(){
        this.writingWriters++;
    }

    /**
     * 使等待写入的线程数量增加
     */
    void incrementWaitingWriters(){
        this.waitingWriters++;
    }

    /**
     * 使读线程的数量增加
     */
    void incrementReadingReaders(){
        this.readingReaders++;
    }

    /**
     * 使写线程的数量减少
     */
    void decrementWritingWriters(){
        this.writingWriters--;
    }

    /**
     * 使等待获取写入锁的线程数量减少
     */
    void decrementWaitingWriters(){
        this.waitingWriters--;
    }

    /**
     * 使读取线程的数量减少
     */
    void decrementReadingReaders(){
        this.readingReaders--;
    }


    /**
     * 获取当前有多少个线程正在进行写操作
     */
    @Override
    public int getWritingWriters() {
        return this.writingWriters;
    }

    /**
     * 获取当前有多少个线程正在等待获取写入锁
     */
    @Override
    public int getWaitingWriters() {
        return this.waitingWriters;
    }

    /**
     * 获取当前有多少个线程正在进行读操作
     */
    @Override
    public int getReadingReaders() {
        return this.readingReaders;
    }

    /**
     * 获取对象锁
     */
    Object getMUTEX(){
        return this.MUTEX;
    }

    /**
     * 获取当前是否偏向写锁
     */
    boolean isPreferWriter(){
        return this.preferWriter;
    }

    /**
     * 设置写锁偏好
     * @param preferWriter
     */
    public void setPreferWriter(boolean preferWriter) {
        this.preferWriter = preferWriter;
    }
}

写锁

/**
 * WriteLock设计为包可见
 */
public class WriteLock implements Lock {
    private final ReadWriteLockImpl readWriteLock;

    WriteLock(ReadWriteLockImpl readWriteLock) {
        this.readWriteLock = readWriteLock;
    }

    @Override
    public void lock() throws InterruptedException {
        synchronized (readWriteLock.getMUTEX()) {
            try {
                // 使等待获取写入锁的线程数量增加
                readWriteLock.incrementWaitingWriters();
                // 如果此时有其他线程正在进行读操作,或者写操作,当前线程挂起
                while (readWriteLock.getReadingReaders() > 0 || readWriteLock.getWritingWriters() > 0) {
                    readWriteLock.getMUTEX().wait();
                }
            } finally {
                //成功获得到了写入锁,使得等待获取写入锁的线程数量减少
                readWriteLock.decrementWaitingWriters();
            }
            // 将正在写入的线程数量增加
            readWriteLock.incrementWritingWriters();


        }
    }

    @Override
    public void unlock() {
        synchronized (readWriteLock.getMUTEX()){
            // 减少正在写入的线程数量
            readWriteLock.decrementWritingWriters();
            // 将偏好修改为false,可使读锁被更快速的所得
            readWriteLock.setPreferWriter(false);
            // 通知唤醒其他在Mutex monitor waitset中的线程
            readWriteLock.getMUTEX().notifyAll();
        }
    }
}

读锁

/**
 * ReadLock设计为包可见
 */
public class ReadLock implements Lock {
    private final ReadWriteLockImpl readWriteLock;

    ReadLock(ReadWriteLockImpl readWriteLock) {
        this.readWriteLock = readWriteLock;
    }

    @Override
    public void lock() {
        // 使用MUTEX作为锁
        synchronized (readWriteLock.getMUTEX()) {
            //若此时有线程正在进行写操作,或者有写线程在等待且偏向写锁的标志位为true时,就无法获得读锁,被挂起
            while (readWriteLock.getWritingWriters() > 0 || (readWriteLock.isPreferWriter() && readWriteLock.getWritingWriters() > 0)) {
                try {
                    readWriteLock.getMUTEX().wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 成功获得读锁,并且使readingReaders的数量增加
            readWriteLock.incrementReadingReaders();
        }

    }

    @Override
    public void unlock() {
        // 使用MUTEX作为锁
        synchronized (readWriteLock.getMUTEX()) {
            // readingReaders的数量减少
            readWriteLock.decrementReadingReaders();
            // 将preferWriter设置为true,可使Writer线程获得更多的机会
            readWriteLock.setPreferWriter(true);
            // 通知唤醒与Mutex关联monitor waitset中的线程
            readWriteLock.getMUTEX().notifyAll();
        }
    }
}

读写锁的使用

DataShare涉及了对数据的读写操作,因此需要进行线程同步控制。

public class ShareData {
    /**
     * 定义共享数据(资源)
     */
    private final List<Character> container = new ArrayList<>();

    /**
     * 构造ReadWriteLock
     */
    private final ReadWriteLock readWriteLock = ReadWriteLock.readWriteLock();

    /**
     * 创建读取锁
     */
    private final Lock readLock = readWriteLock.readLock();

    /**
     * 创建写入锁
     */
    private final Lock writeLock = readWriteLock.writeLock();

    private final int length;

    public ShareData(int length) {
        this.length = length;
        for (int i = 0; i < length; i++) {
            container.add(i,'c');
        }
    }

    public char[] read() throws InterruptedException{
        try{
            // 使用读锁进行read
            readLock.lock();
            char[] newBuffer = new char[length];
            for (int i=0;i<length;i++){
                newBuffer[i] = container.get(i);
            }
            slowly();
            return newBuffer;
        }finally {
            // 操作结束,将锁释放
            readLock.unlock();
        }

    }

    public void write(int index,char c) throws InterruptedException{
        try{
            // 使用写锁进行lock
            writeLock.lock();
            this.container.set(index, c);
            slowly();
        }finally {
            // 操作完成后,释放写锁
            writeLock.unlock();
        }
    }

    private void slowly(){
        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}

创建2个写线程,10个读线程。

public class ReadWriteLockTest {
    //This is the example for read write lock
    private final static String text = "Thisistheexampleforreadwritelock";

    public static void main(String[] args) {
        // 定义共享数据
        final ShareData shareData = new ShareData(50);
        // 创建两个线程进行写操作
        for (int i = 0; i < 2; i++) {
            new Thread(){
                @Override
                public void run(){
                    for (int index = 0;index<text.length();index++){

                        try{
                            TimeUnit.SECONDS.sleep(5);
                            char c = text.charAt(index);
                            shareData.write(index,c);
                            System.out.println(currentThread() + " write "+c);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }

        // 创建4个线程进行写操作
        for (int i=0;i<10;i++){
            new Thread(){
                @Override
                public void run(){
                    while (true){
                        try{
                            TimeUnit.SECONDS.sleep(1);
                            System.out.println(currentThread()+" read "+new String(shareData.read()));
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    }
}

总结

在读操作较多的情况下,读写锁的性能要优于synchronized关键字。

Reference

《Java高并发编程详解》