提升--22---ReentrantReadWriteLock读写锁

2023-12-13 03:51:09

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


ReadWriteLock----读写锁

1.读写锁介绍

现实中有这样一种场景

对共享资源有读和写的操作,且写操作没有读操作那么频繁(读多写少)
在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源(读读可以并发)
但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写操作了(读写,写读,写写互斥)
在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。

针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它内部,维护了一对相关的锁,一个用于只读操作,称为读锁;一个用于写入操作,称为写锁

这个ReadWriteLock 是读写锁。读写锁的概念其实就是共享锁和排他锁,读锁就是共享锁,写锁就是排他锁

  • 读锁 —共享锁
  • 写锁 —排他锁

线程进入读锁的前提条件:

  • 没有其他线程的写锁
  • 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

线程进入写锁的前提条件:

  • 没有其他线程的读锁
  • 没有其他线程的写锁

而读写锁有以下三个重要的特性:

  1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
  2. 可重入:读锁和写锁都支持线程重入。以读写线程为例:读线程获取读锁后,能够再次获取读锁。写线程在获取写锁之后能够再次获取写锁,同时也可以获取读锁。
  3. 锁降级:遵循获取写锁、再获取读锁最后释放写锁的次序,写锁能够降级成为读锁。

ReentrantReadWriteLock的使用

  • 我们这有两个方法,read()读一个数据,write()写一个数据。read这个数据的时候我需要你往里头传一把锁,这个传那把锁你自己定,我们可以传自己定义的全都是排他锁,也可以传读写锁里面的读锁或写锁。write的时候也需要往里面传把锁,同时需要你传一个新值,在这里值里面传一个内容。我们模拟这个操作,读的是一个int类型的值,读的时候先上锁,设置一秒钟,完了之后read over,最后解锁unlock。再下面写锁,锁定后睡1000毫秒,然后把新值给value,write over后解锁,非常简单。
  • new ReentrantReadWriteLock() 是ReadWriteLock的一种实现,在这种实现里头我又分出两把锁来,一把叫readLock,一把叫writeLock,通过他的方法readWriteLock.readLock()来拿到readLock对象,读锁我就拿到了。通过readWriteLock.writeLock()拿到writeLock对象。这两把锁在我读的时候扔进去,因此,读线程是可以一起读的,也就是说这8个线程可以一秒钟完成工作结束。所以使用读写锁效率会大大的提升。
package com.cy.month12;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class T10_TestReadWriteLock {

    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
            //模拟读取操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //模拟写操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {

        Runnable readR = ()-> read(readLock);

        Runnable writeR = ()->write(writeLock, new Random().nextInt());

        for(int i=0; i<8; i++) new Thread(readR).start();
        for(int i=0; i<2; i++) new Thread(writeR).start();
    }
}

在这里插入图片描述

10、ReentrantReadWriteLock读写锁出现的目的?

首先,读写锁能解决的,互斥锁肯定能解决,但是,互斥锁的效率可能比较低。

比如说有一个业务,平均下来,10次读操作,1次写操作。

如果用互斥锁,可以保证线程安全,但是10次读操作也需要一个一个来。

但是,读读操作没有线程安全问题。

但是只要涉及到了写操作,比如读写操作,需要保证线程安全。只要有写线程,就必须满足互斥性。

所以JUC下就提供了一个ReentrantReadWriteLock,读写锁。这个锁的特点,就是读读可以一起操作,但是只要涉及到了写操作,就必须保证互斥。

11、ReentrantReadWriteLock如何基于AQS实现的?

lock锁,无论是读锁还是写锁,都是基于state属性判断的。

state是int,占32个bit为,将高16为,作为读锁的标识,低16位,作为写锁的标识。

如果线程要拿读锁,只需要确认没有写线程在持有写锁资源,并且队列中的head.next不是写锁(解决写锁饥饿问题),就可以直接获取读锁资源。

写锁需要确保没有读线程在持有读锁,并且没有其他线程在持有写锁,写锁才能拿到锁资源。

文章来源:https://blog.csdn.net/weixin_48052161/article/details/134797043
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。