1.ReentrantReadWriteLock的特性说明
1.1获取锁顺序
- 非公平模式(默认):当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。
- 公平模式:当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。
1.2可重入
- 什么是可重入锁,不可重入锁呢?"重入"字面意思已经很明显了,就是可以重新进入。可重入锁,就是说一个线程在获取某个锁后,还可以继续获取该锁,即允许一个线程多次获取同一个锁。
1.3锁降级
- 什么是锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。
- 注意点:锁降级之后,写锁并不会直接降级成读锁,不会随着读锁的释放而释放,因此需要显式地释放写锁 。
- 锁降级的应用场景:用于对数据比较敏感,需要在对数据修改之后,获取到修改后的值,并进行接下来的其他操作 。
- 是否有锁升级:在ReentrantReadWriteLock里面,不存在锁升级的说法。
1.4读写锁特性
- 写写互斥,读写互斥,读读共享
2.模式多线程读写锁实例
public class ReentrantReadWriteLockDemo { private ReadWriteLock readWriteLock = new ReentrantReadWriteLock (); private Lock readLock = readWriteLock.readLock (); private Lock writeLock = readWriteLock.writeLock (); private int i = 0; private int j = 0; public void readMethod() { // 读锁 readLock.lock (); try { System.out.println (String.format ("线程%s获得i======》%d,获得j======》%d", Thread.currentThread ().getName (), i, j)); } finally { // 释放锁 readLock.unlock (); } } public void writeMethod() { // 写锁 writeLock.lock (); try { i++; j++; System.out.println (String.format ("线程%s正在写入i====>%d,j=====>%d", Thread.currentThread ().getName (), i, j)); } finally { // 释放锁 writeLock.unlock (); } } public static void main(String[] args) { ReentrantReadWriteLockDemo readWriteLockDemo = new ReentrantReadWriteLockDemo (); // 模拟十个写入线程 for (int i = 0; i < 10; i++) { new Thread (() -> { readWriteLockDemo.writeMethod (); }).start (); } // 模拟三个读线程 for (int j = 0; j < 3; j++) { new Thread (() -> { readWriteLockDemo.readMethod (); }).start (); } } }
- 控制台输出:
ps:ReentrantReadWriteLock是ReadWriteLock接口的实现,ReadWriteLock提供了两个接口readLock和writeLock;通过调用readLock()和writeLock()方法直接返回了readLock和writeLock两个实例对象。ReadLock和WriteLock是ReentrantReadWriteLock下面的静态内部类。而这两个静态内部类都实现了Lock接口。调用lock方法之后就用到了AQS的doAcquireShared()方法。AQS用单一的int值表示读写值位置,int是32位,将其才分成无符号的两个short,高位表示读锁,低位表示写锁。两个锁的最大次数为2的16次方减1。
3.模拟锁降级
public class ReentrantReadWriteLockDemo2 { private ReadWriteLock readWriteLock = new ReentrantReadWriteLock (); /** 读锁 */ private Lock readLock = readWriteLock.readLock (); /** 写锁 */ private Lock writeLock = readWriteLock.writeLock (); public static void main(String[] args) { ReentrantReadWriteLockDemo2 readWriteLockDemo2 = new ReentrantReadWriteLockDemo2 (); // 获取写锁 readWriteLockDemo2.writeLock.lock (); // 执行写入操作 System.out.println ("开始写入"); // 获取读锁 PS:没有释放写锁,直接获取读锁,是锁降级 readWriteLockDemo2.readLock.lock (); // 释放写锁 PS:如果写入一直没被释放,会导致线程死锁 readWriteLockDemo2.writeLock.unlock (); // 执行读取操作 System.out.println ("开始读取"); // 释放读锁 readWriteLockDemo2.readLock.unlock (); // 创建一个线程模拟继续写入,如果在锁降级后没有释放写锁,由于写写互斥,造成线程死锁 new Thread (() ->{ readWriteLockDemo2.writeLock.lock (); System.out.println ("继续写入"); }).start (); } }
- 控制台输出:
ps:读写锁特性:写写互斥、读写互斥、读读共享
4.演示锁升级错误操作
public class ReentrantReadWriteLockDemo3 { private ReadWriteLock readWriteLock = new ReentrantReadWriteLock (); private Lock readLock = readWriteLock.readLock (); private Lock writeLock = readWriteLock.writeLock (); public static void main(String[] args) { ReentrantReadWriteLockDemo3 readWriteLockDemo3 = new ReentrantReadWriteLockDemo3 (); // 获取读锁 readWriteLockDemo3.readLock.lock (); // 执行读操作 System.out.println ("开始读取"); /** * 获取写锁 PS:在没有释放读锁,获取写锁,为锁升级操作, * ReentrantReadWriteLock不支持锁升级,线程死锁 */ readWriteLockDemo3.writeLock.lock (); // 开始写入 System.out.println ("开始写入"); // 释放写操作 readWriteLockDemo3.writeLock.unlock (); } }
- 控制台输出:
- 使用jconsole查看:
5.总结
- ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
- ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁。写写互斥、读写互斥、读读共享
- ReetrantReadWriteLock实现了ReadWriteLock接口并实现了可重入的特性
- ReetrantReadWriteLock读锁的效率明显高于synchronized关键字,因为ReetrantReadWriteLock读锁是共享模式,运行线程并发执行,而synchronized只能让拥有锁的线程去读取,所以读取的效率没有ReetrantReadWriteLock高。
链接: java多线程:详解StampedLock的特性,悲观读和乐观读,排他写