读写锁ReentrantReadWriteLock用法详解

2022年7月18日13:15:13

读写锁ReentrantReadWriteLock用法详解

前言

业务开发中我们可能涉及到读写操作。
面对写和读,对于数据同步,在使用Lock锁和synchronized关键字同步数据时候,对于读读而言,两个线程也需要争抢锁,此时额外争抢锁是没有意义的,造成性能损耗,写的时候,不能读,没有写的时候,读线程不能互斥。

对于Lock锁和synchronized 来说。都是互斥锁,读读也存在互斥。对此,我们需要读写锁。

如实例:

生产者和消费者而言

当一个线程负责生产,2个线程负责消费,生产者没有进行生产时,两个消费线程都可以去消费数据(这里我们不考虑 重复数据问题)

两个线程彼此还要争抢资源

privatestaticfinalint LINED_SIZE=1000;privatestaticint num=0;privatestaticfinalObject lock=newObject();privatestaticfinalLinkedList<Integer> linkedList=newLinkedList<>();publicstaticvoidmain(String[] args)throwsInterruptedException{




        t1.start();
        t2.start();
        t3.start();

        t1.setPriority(5);
        t2.setPriority(5);
        t3.setPriority(5);

        t1.join();
        t2.join();
        t3.join();TimeUnit.SECONDS.sleep(2);System.out.println(" main  end ");}staticclassConsumerObjeimplementsRunnable{@Overridepublicvoidrun(){while(true){synchronized(lock){while(linkedList.size()==0){try{
                           lock.wait();}catch(InterruptedException e){
                           e.printStackTrace();}}try{Thread.sleep(5_00);}catch(InterruptedException e){
                       e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" : "+ linkedList.removeFirst());
                   lock.notifyAll();}}}}staticclassProductObjeimplementsRunnable{@Overridepublicvoidrun(){while(true){synchronized(lock){while(linkedList.size()>= LINED_SIZE){try{
                            lock.wait();}catch(InterruptedException e){
                            e.printStackTrace();}}int n= num++;System.out.println(" 正在生产: "+ n);
                    linkedList.addLast(n);
                    lock.notifyAll();}}}}

思考

  • 为什么需要使用读写锁ReentrantReadWriteLock

  • 这个锁有什么好处?缺点?

好处: 读读不能互斥,提升锁性能,减少线程竞争。
缺点是:当读锁过多时候,写锁少,存在锁饥饿现象。

读写锁ReentrantReadWriteLock用法详解

ReentrantReadWriteLock 也提供了公平和非公平锁

基于构造默认非公平锁, ReentrantReadWriteLock 读写锁内部也是基于AQS队列实现的。

publicReentrantReadWriteLock(){this(false);}
//读写锁privatestaticReentrantReadWriteLock readWriteLock=newReentrantReadWriteLock(true);//写锁privatefinalstaticLock writeLock= readWriteLock.writeLock();//读锁privatefinalstaticLock readLock= readWriteLock.readLock();privatefinalstaticList<Long> longs=newArrayList<>();publicfinalstaticvoidmain(String[] args)throwsInterruptedException{//        new Thread(ReentrantReadWriteLockTest::write).start();//        TimeUnit.SECONDS.sleep(1);//        new Thread(ReentrantReadWriteLockTest::write).start();newThread(ReentrantReadWriteLockTest::write).start();TimeUnit.SECONDS.sleep(1);newThread(ReentrantReadWriteLockTest::read).start();newThread(ReentrantReadWriteLockTest::read).start();}staticvoidwrite(){try{
            writeLock.lock();TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName()+" write ");
            longs.add(System.currentTimeMillis());}catch(InterruptedException e){
            e.printStackTrace();}finally{
            writeLock.unlock();}}staticvoidread(){try{
            readLock.lock();TimeUnit.SECONDS.sleep(1);
            longs.forEach(x->System.out.println(x));}catch(InterruptedException e){
            e.printStackTrace();}finally{
            readLock.lock();}}

可以看到我们写了一条数据,两条数据同时打印出来,读读是不互斥的。

Thread-0 write
1648997092090
1648997092090

读写锁 存在一个问题:
当读锁比例很多,写锁很少,锁竞争情况下,写锁抢到锁的机会就回少,读锁数量太大的情况下,写锁不一定能抢到锁.

我们使用非公平锁,来测试,启动5个读锁,一个写锁。

//读写锁privatestaticReentrantReadWriteLock readWriteLock=newReentrantReadWriteLock(false);//写锁privatefinalstaticLock writeLock= readWriteLock.writeLock();//读锁privatefinalstaticLock readLock= readWriteLock.readLock();privatefinalstaticList<Long> longs=newArrayList<>();publicfinalstaticvoidmain(String[] args)throwsInterruptedException{newThread(ReentrantReadWriteLockTest2::write).start();TimeUnit.SECONDS.sleep(1);//new Thread(ReentrantReadWriteLockTest2::read).start();//new Thread(ReentrantReadWriteLockTest2::read).start();for(int i=0; i<5; i++){newThread(ReentrantReadWriteLockTest2::read).start();}}staticvoidwrite(){for(;;){try{
               writeLock.lock();TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName()+" write ");
               longs.add(System.currentTimeMillis());}catch(InterruptedException e){
               e.printStackTrace();}finally{
               writeLock.unlock();}}}staticvoidread(){for(;;){try{
                  readLock.lock();TimeUnit.SECONDS.sleep(1);
                  longs.forEach(x->System.out.println(x));}catch(InterruptedException e){
                  e.printStackTrace();}finally{
                  readLock.lock();}}}

测试结果这里就不写了,刚开始一直写,后来一直读,写锁机会很少,当读线程比例再大时,写的机会就更少了。

最后

ReentrantReadWriteLock 读写锁既有有点也有缺点

好处: 读读不能互斥,提升锁性能,减少线程竞争。
缺点是:当读锁过多时候,写锁少,存在锁饥饿现象。

使用时候需要控制读写比例,防止出现锁饥饿现象。

当出现读比例特别大时候,ReentrantReadWriteLock锁就不适合了,此时JDK8之后提供的StampedLock锁更适合读写比例大的场景

  • 作者:Flechazo`
  • 原文链接:https://pilgrim.blog.csdn.net/article/details/123944882
    更新时间:2022年7月18日13:15:13 ,共 4088 字。