0
点赞
收藏
分享

微信扫一扫

ReentrantReadWriteLock实现原理探索


一、读写锁基本特性

 

锁分离的思想。

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

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

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

       

ReentrantReadWriteLock实现原理探索_读锁

 

二、读写锁基本实现原理

 

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

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

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

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

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

        实现原理也很简单。

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

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

锁降级

ReentrantReadWriteLock实现原理探索_读锁_02

三、锁降级

 

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

当前线程 A 无法感知线程 B 的数据更新。如果当前线程 A 获取读锁,即遵循降级的步骤(获取写锁,再获取读锁,再释放写锁),则线程 B 将会被阻塞,直到当前线程 A 使用数据并释放读锁之后,线程 B 才能获取写锁进行数据更新。

ReentrantReadWriteLock实现原理探索_读锁_03

线程在持有写锁期间,在线程任务方法内,获取了读锁,再释放掉写锁,当前线程将只持有读锁。其它希望获取写锁的线程阻塞,因为读写锁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();

}
}

ReentrantReadWriteLock实现原理探索_读写锁_04

 

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

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

      ​

 

 

 

 

 

 

 

 

 

 

 

 

 

举报

相关推荐

0 条评论