Java多线程:读写锁详解

后端 潘老师 1年前 (2023-11-10) 176 ℃ (0) 扫码查看

前面讲到的 synchronized 内部锁ReentrantLock 都是独占锁(排他锁),同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低。

ReentrantLock是一种排他锁,同一时刻只允许一个线程访问,ReadWriteLock 接口的实现类 ReentrantReadWriteLock 读写锁提供了两个方法:readLock()和writeLock()用来获取读锁和写锁,也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。

读写锁是什么

读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。

总结来说,读写锁的特点是:读读共享、读写互斥、写写互斥。

ReadWriteLock接口

public interface ReadWriteLock {
    
    // 读锁
    Lock readLock();
   
    // 写锁
    Lock writeLock();
}

读写锁维护了两个锁,一个是读操作相关的锁也称为共享锁,一个是写操作相关的锁也称为排他锁。通过分离读锁和写锁,其并发性比一般排他锁有了很大提升。

ReentrantReadWriteLock 读写锁

ReentrantReadWriteLock 读写锁实现了ReadWriteLock 接口,我们先看下基本使用:

基本使用

//定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//获得读锁
Lock readLock = rwLock.readLock();
//获得写锁
Lock writeLock = rwLock.writeLock();
//读数据
readLock.lock();
try {
    //读数据
} finally {
    readLock.unlock();
}

//写数据
try {
    writeLock.lock();
    //写数据
} finally {
    writeLock.unlock();
}

读读共享

从下面这段代码的运行状态中可以看出,创建的5个线程都同时拿到了读锁,而不用每隔3秒拿一个。说明读锁是共享的。

/**
 * ReadWriteLock读写锁允许多个线程同时读,即读读共享
 */
public class Test01 {

    static class Service {
        //定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        public void read() {
            //定义方法读取数据
            readWriteLock.readLock().lock();  //申请读锁
            try {
                System.out.println(Thread.currentThread().getName() + "获得读锁");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.readLock().unlock();  //释放读锁
            }
        }
    }

    public static void main(String[] args) {
        Service service = new Service();

        for(int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.read();
                }
            }).start();
        }
    }
}

写写互斥

从下面的例子中可以看出来,写写是互斥的,每个线程必须依次获得写锁:

public class Test02 {

    static class Service {
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        public void write() {
            readWriteLock.writeLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得了写锁");
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readWriteLock.writeLock().unlock();
            }
        }
    }

    public static void main(String[] args) {
        Service service = new Service();
        for(int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.write();
                }
            }).start();
        }
    }
}

读写互斥

一个线程获得读锁,写线程等待。一个线程获得写锁,其他线程等待。

public class Test03 {

    static class Service {
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private Lock readLock = readWriteLock.readLock();
        private Lock writeLock = readWriteLock.writeLock();

        public void read() {
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "读取数据:" + System.currentTimeMillis());
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
            }
        }

        public void write() {
             writeLock.lock();            
             try {
                System.out.println(Thread.currentThread().getName() + "写数据:" + System.currentTimeMillis());
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        }).start();
        for(int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.write();
                }
            }).start();
            TimeUnit.MILLISECONDS.sleep(500);
        }
        for(int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    service.read();
                }
            }).start();
        }
    }
}

 总结

多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥(只要出现写操作的过程就是互斥的)。在没有线程进行写操作时,进行读取操作的多个线程都可以获取读锁,而进行写入操作的线程只有在获取写锁后才能进行写操作。即多个线程可以同时进行读取操作,但是同一时刻只允许一个线程进行写入操作。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/10965.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】