java的读写锁_不是我吹!Java的读写锁,看这篇就够了!

读写锁的基本使用

在【ReentrantLock锁详解】一文中讲到了java中锁的划分,本篇主要讲述共享锁和排他锁:ReentrantReadWriteLock

在ReentrantReadWriteLock中包含读锁和写锁,其中读锁是可以多线程共享的,即共享锁,而写锁是排他锁,在更改时候不允许其他线程操作。读写锁其实是一把锁,所以会有同一时刻不允许读写锁共存的规定。之所以要细分读锁和写锁也是为了提高效率,将读和写分离,对比ReentrantLock就可以发现,无论并发读还是写,它总会先锁住全部再说。

接着先来个代码演示下,读锁是共享锁,写锁是排他锁:

/**

* ReentrantReadWriteLock读写锁示例

**/

public class ReentrantReadWriteLockTest {

private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();

private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();

private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();

public static void read() {

readLock.lock();

try {

System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");

Thread.sleep(1000);

} catch (Exception e) {

e.printStackTrace();

} finally {

readLock.unlock();

System.out.println(Thread.currentThread().getName() + "释放读锁");

}

}

public static void write() {

writeLock.lock();

try {

System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");

Thread.sleep(1000);

} catch (Exception e) {

e.printStackTrace();

} finally {

writeLock.unlock();

System.out.println(Thread.currentThread().getName() + "释放写锁");

}

}

public static void main(String[] args) {

new Thread(() -> read(), "Thread1").start();

new Thread(() -> read(), "Thread2").start();

new Thread(() -> write(), "Thread3").start();

new Thread(() -> write(), "Thread4").start();

}

}

输出结果如下,线程1和线程2可以同时获取读锁,而线程3和线程4只能依次获取写锁,因为线程4必须等待线程3释放写锁后才能获取到锁:

Thread1获取读锁,开始执行

Thread2获取读锁,开始执行

Thread1释放读锁

Thread2释放读锁

Thread3获取写锁,开始执行

Thread3释放写锁

Thread4获取写锁,开始执行

Thread4释放写锁

读锁的插队策略

设想如下场景:在非公平的ReentrantReadWriteLock锁中,线程2和线程4正在同时读取,线程3想要写入,拿不到锁(同一时刻是不允许读写锁共存的),于是进入等待队列,线程5不在队列里,现在过来想要读取,策略1是如果允许读插队,就是说线程5读先于线程3写操作执行,因为读锁是共享锁,不影响后面的线程3的写操作,这种策略可以提高一定的效率,却可能导致像线程3这样的线程一直在等待中,因为可能线程5读操作之后又来了n个线程也进行读操作,造成线程饥饿;策略2是不允许插队,即线程5的读操作必须排在线程3的写操作之后,放入队列中,排在线程3之后,这样能避免线程饥饿。

事实上ReentrantReadWriteLock在非公平情况下,读锁采用的就是策略2:不允许读锁插队,避免线程饥饿。更加确切的说是:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程。

以上还在非公平ReentrantReadWriteLock锁中,在公平锁中,读写锁都是是不允许插队的,严格按照线程请求获取锁顺序执行。

下面用代码演示一下结论:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程

/**

* ReentrantReadWriteLock读写锁插队策略测试

**/

public class ReentrantReadWriteLockTest {

private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();

private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();

private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();

public static void read() {

System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");

readLock.lock();

try {

System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");

Thread.sleep(20);

} catch (Exception e) {

e.printStackTrace();

} finally {

readLock.unlock();

System.out.println(Thread.currentThread().getName() + "释放读锁");

}

}

public static void write() {

System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");

writeLock.lock();

try {

System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");

Thread.sleep(40);

} catch (Exception e) {

e.printStackTrace();

} finally {

System.out.println(Thread.currentThread().getName() + "释放写锁");

writeLock.unlock();

}

}

public static void main(String[] args) {

new Thread(() -> write(), "Thread1").start();

new Thread(() -> read(), "Thread2").start();

new Thread(() -> read(), "Thread3").start();

new Thread(() -> write(), "Thread4").start();

new Thread(() -> read(), "Thread5").start();

new Thread(() -> {

Thread[] threads = new Thread[1000];

for (int i = 0; i < 1000; i++) {

threads[i] = new Thread(() -> read(), "子线程创建的Thread" + i);

}

for (int i = 0; i < 1000; i++) {

threads[i].start();

}

}).start();

}

}

以上测试代码就演示了,在非公平锁时,其一:同一时刻读写锁不能同时存在, 其二,读锁非常容易插队,但前提是队列中的头结点不能是想获取写锁的线程。

锁的升降级

升降级是指读锁升级为写锁,写锁降级为度锁。在ReentrantReadWriteLock读写锁中,只支持写锁降级为读锁,而不支持读锁升级为写锁,如下代码测试所示:

/**

* ReentrantReadWriteLock锁升降级测试

**/

public class ReentrantReadWriteLockTest {

private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();

private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();

private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();

public static void read() {

System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");

readLock.lock();

try {

System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");

Thread.sleep(20);

System.out.println(Thread.currentThread().getName()+ "尝试升级读锁为写锁");

//读锁升级为写锁(失败)

writeLock.lock();

System.out.println(Thread.currentThread().getName() +"读锁升级为写锁成功");

} catch (Exception e) {

e.printStackTrace();

} finally {

readLock.unlock();

System.out.println(Thread.currentThread().getName() + "释放读锁");

}

}

public static void write() {

System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");

writeLock.lock();

try {

System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行"); Thread.sleep(40);

System.out.println(Thread.currentThread().getName() +"尝试降级写锁为读锁");

//写锁降级为读锁(成功)

readLock.lock();

System.out.println(Thread.currentThread().getName()+ "写锁降级为读锁成功");

System.out.println();

} catch (Exception e) {

e.printStackTrace();

} finally {

System.out.println(Thread.currentThread().getName() + "释放写锁"); writeLock.unlock() readLock.unlock();

}

}

public static void main(String[] args) {new Thread(() -> write(), "Thread1").start();

new Thread(() -> read(), "Thread2").start();

}

}

运行控制台输出:

Thread1开始尝试获取写锁

Thread1获取写锁,开始执行

Thread1尝试降级写锁为读锁

Thread1写锁降级为读锁成功

Thread1释放写锁

Thread2开始尝试获取读锁

Thread2获取读锁,开始执行

Thread2尝试升级读锁为写锁

之所以ReentrantReadWriteLock不支持锁的升级(其它锁可以支持),主要是避免死锁,例如两个线程A和B都在读, A升级要求B释放读锁,B升级要求A释放读锁,互相等待形成死循环。如果能严格保证每次都只有一个线程升级那也是可以的。

总结读写锁特点特点:读锁是共享锁,写锁是排他锁,读锁和写锁不能同时存在

插队策略:为了防止线程饥饿,读锁不能插队

升级策略:只能降级,不能升级

ReentrantReadWriteLock适合于读多写少的场合,可以提高并发效率,而ReentrantLock适合普通场合

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值