ReentrantReadWriteLock实现原理探索

本文深入探讨了ReentrantReadWriteLock的特性和实现原理,包括锁分离、公平性、重进入和锁降级等概念,以及如何通过AQS实现读写锁的并发控制,确保数据安全。

一、读写锁基本特性

 

        我们知道,对锁性能的优化其中有一条:如果操作互不影响,那么锁就可以被分离。这就是锁分离的思想。

        ReentrantReadWriteLock可重入的读写锁。读写锁在同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和其他线程均被阻塞。

        ReentrantReadWriteLock内部有两把锁,一把读锁,一把写锁。通过分离读锁和写锁,使得它比其它排他锁性能更好。

        ReentrantReadWriteLock有下面三个特性:公平性、重进入、锁降级。

       

 

二、读写锁基本实现原理

 

        ReentrantReadWriteLock 内部也是通过 队列同步器AQS 实现的(看来AQS是开启并发编程的关键突破口!)。

        AQS 中的一个同步状态 state 表示当前共享资源是否被其他线程锁占用。如果为0则表示未被占用,其他值表示该锁被重入的次数。

        如何才能在一个int类型的变量上,记录 读锁与写锁的 状态?

        用 按位切割 将这个变量分为两部分,高16位记录读锁的状态,低16位记录写锁的状态 

        下图中,当前状态有一个线程已经获取了写锁,且重入了两次,同时也获取了两次读锁。

        实现原理也很简单。

        (1)如果写状态的值为0,读状态的值不为0,那么当前线程获取的就是读锁。

        (2)如果读状态的值为0,写状态的值不为0,那么当前线程获取的就是写锁。

        (3)如果读状态的值与写状态的值均不为0,那么不用说了,当前线程获取的是写锁。这个原因是:锁降级

三、锁降级

 

        锁降级的最终目的是:保证共享变量的数据安全。

        假如有一个线程 A 已经获取了写锁,并且修改了数据,如果当前线程 A 不获取读锁而直接释放写锁,此时,另一个线程 B 获取到了写锁并修改了数据,那么当前线程 A 无法感知线程 B 的数据更新。如果当前线程 A 获取读锁,即遵循降级的步骤(获取写锁,再获取读锁,再释放写锁),则线程 B 将会被阻塞,直到当前线程 A 使用数据并释放读锁之后,线程 B 才能获取写锁进行数据更新。

        所以说,线程在持有写锁期间,在线程任务方法内,获取了读锁,再释放掉写锁,当前线程将只持有读锁。其它希望获取写锁的线程阻塞,因为读写锁ReentrantReadWriteLock是读写互斥的。其它希望获取读锁的线程不会被阻塞,因为多个线程可以共享读锁。因此,在代码中,线程在获取写锁后的任务代码中,需要获取一次读锁。

        总结:如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。

 

        纸上得来终觉浅,绝知此事要躬行

/**
 * Created by jay.zhou on 2018/9/14.
 */
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Created by Jay.Zhou on 2018/9/14.
 */
public class ReentrantReadWriteLockDemo {
    //读写锁对象,构造函数弄成false,保证是非公平锁
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(false);
    //多个线程准备操作的共享变量
    private int value;

    /**
     * 操作共享变量value的方法A
     */
    public void writeA() {
        //第一步:获取到写锁,准备写入
        Lock writeLock = readWriteLock.writeLock();
        //写锁加锁
        writeLock.lock();
        //执行任务,操作共享变量
        for (int i = 0; i < 10000; i++) {
            value++;
        }
        System.out.println("writeA操作完毕,共享变量的值是:"+value);
        //任务执行完毕,获取读锁
        Lock readLock = readWriteLock.readLock();
        //读锁加锁
        readLock.lock();
        //释放写锁,当前线程就只持有读锁,可以阻塞其它的写线程
        writeLock.unlock();
        //让这个方法延迟5秒钟,模拟
        // 1.其它读线程读取共享变量
        // 2.其它写线程被阻塞
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //任务执行完毕,释放其它的写锁
        readLock.unlock();
    }

    /**
     * 操作共享变量value的方法B
     */
    public void writeB() {
        //写任务需要获取锁,阻塞其它线程
        Lock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        this.value = 9999;
        //任务执行完毕,获取共享变量的值
        System.out.println("writeB操作完毕,共享变量的值是:"+value);
        writeLock.unlock();
    }

    //获取共享变量
    public void get() {
        //读取共享变量的时候,不允许写线程进入,因此需要加上读锁
        Lock readLock = readWriteLock.readLock();
        //上锁
        readLock.lock();
        //读取共享变量
        System.out.println("读取任务操作完毕,共享变量的值是:"+value);
        //释放读锁
        readLock.unlock();
    }


    public static void main(String[] args) throws InterruptedException {
        ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
        //创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        pool.execute(()->{
            //执行写任务
            //先获取写锁,再获取读锁,最后释放写锁。
            //这样此线程持有的锁将会降级为读锁,阻塞其它的写入线程
            //在此期间,writeB写方法将会被阻塞,而get读方法可以获取到共享变量
            demo.writeA();
        });
        Thread.sleep(100);
        //开启第二个线程
        pool.execute(()->{
            //第二个读线程
             demo.get();

        });
        Thread.sleep(100);
        //开启第三个线程
        pool.execute(()->{
            //第三个写线程尝试操作共享变量
            demo.writeB();
        });
        pool.shutdown();

    }
}

 

        上面的程序,我执行了好几次,如果把writeB()方法放到get()方法之前执行,可能结果就有点问题。

        我猜测,如果 A线程占了读锁  , 那么B写线程 将会被阻塞。  再来了一个C线程,需要排队到B线程后。

        参考博客:https://blog.csdn.net/yanyan19880509/article/details/52435135

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

### 回答1: ReentrantReadWriteLock是Java中的一个锁机制,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。它的原理是基于读写锁的概念,读锁和写锁是互斥的,但读锁之间不互斥,因此多个线程可以同时读取共享资源,而写锁和读锁、写锁之间都是互斥的,因此只有一个线程可以写入共享资源。此外,ReentrantReadWriteLock还支持重入,即同一个线程可以多次获取读锁或写锁,这样可以避免死锁的发生。 ### 回答2: ReentrantReadWriteLock(可重入读写锁)是Java并发包中的一个工具类,用于控制多线程对共享资源的访问。它是一种读写锁机制,支持同一时间多个线程进行读操作,但只允许一个线程进行写操作。 reentrantreadwritelock原理是基于两个重要的概念:读锁和写锁。当一个线程获得读锁时,其他线程也可以获取读锁,以允许多个线程同时读取共享资源。而当一个线程获得写锁时,其他线程需要等待该线程释放写锁后才能进行写操作。 reentrantreadwritelock通过内部的AQS(AbstractQueuedSynchronizer)实现,它使用了一种先进先出的等待队列来管理线程的获取和释放锁的顺序。在实现过程中,reentrantreadwritelock会维护读锁和写锁的数量统计,并根据这些统计信息来判断是否可以获取锁或释放锁。 当一个线程尝试获取读锁时,如果当前没有其他线程持有写锁,那么该线程可以立即获得读锁;如果有其他线程持有写锁,那么需要进入等待队列等待写锁释放。而当一个线程尝试获取写锁时,如果当前没有其他线程持有读锁或写锁,那么该线程可以立即获得写锁;如果有其他线程持有读锁或写锁,那么需要进入等待队列等待读锁和写锁都释放。 此外,reentrantreadwritelock还具有重入性,即同一个线程可以重复获取同一把锁,而不会造成死锁。当一个线程重复获取锁时,会将锁的计数器加一,并在释放锁时将计数器减一。只有在计数器为零时,其他线程才能获取锁。 总体来说,reentrantreadwritelock通过管理读锁和写锁的获取和释放顺序,以及锁的重入性,实现对共享资源的高效访问控制。它可以提高系统的并发性能和效率,避免访问冲突和数据不一致的问题。 ### 回答3: ReentrantReadWriteLock是Java中的一个锁机制,可以实现对于共享资源的读写操作。它的原理是基于读写锁的概念。 在传统的锁机制中,每次只能有一个线程访问共享资源,这样会导致性能降低,特别是对于读操作频繁的场景。而ReentrantReadWriteLock通过实现读写分离的机制来提高多线程环境下的读操作效率。 ReentrantReadWriteLock内部有两个锁,分别是读锁和写锁。多个线程可以同时获取读锁,但只有一个线程可以获取写锁,当有线程获取写锁时,其他线程都无法获取读锁和写锁,实现了对于共享资源的排他性保护。 在读锁的机制中,当读锁被一个线程获取后,其他线程可以继续获取读锁,但写锁无法被获取。这样可以保证并发读不会受到阻塞,提高了读操作的效率。当没有线程持有读锁时,写锁才能被获取。 在写锁的机制中,当写锁被一个线程获取后,其他线程无法获取读锁和写锁。这样可以保证在写操作进行时,其他线程无法读写共享资源,实现了对于共享资源的排他性保护。 ReentrantReadWriteLock还提供了可重入性的特性,即同一个线程可以重复获取同一把锁。这样可以避免了死锁的问题,并且提供了更大的灵活性和便利性。 总的来说,ReentrantReadWriteLock通过读写分离的机制和可重入性特性,实现了对于共享资源的高效且安全的读写操作。它在具有大量读操作和少量写操作的多线程环境中,能够提供更好的性能和并发控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小大宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值