ReentrantReadWriteLock实现原理探索

一、读写锁基本特性

 

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

        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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小大宇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值