Java并发 --- 锁相关问题

2021-04-27  本文已影响0人  _code_x

Java中有两种加锁的方式:一种是用synchronized关键字,另一种是用Lock接口的实现类。

如果你只是想要简单的加个锁,对性能也没什么特别的要求,用synchronized关键字就足够了。自Java 5之后,才在java.util.concurrent.locks包下有了另外一种方式来实现锁,那就是Lock。也就是说,synchronized是Java语言内置的关键字,而Lock是一个接口,这个接口的实现类在代码层面实现了锁的功能。

ReentrantLock、ReadLock、WriteLock 是Lock接口最重要的三个实现类。对应了“可重入锁”、“读锁”和“写锁”。

ReadWriteLock其实是一个工厂接口,而ReentrantReadWriteLock是ReadWriteLock的实现类,它包含两个静态内部类ReadLock和WriteLock。这两个静态内部类又分别实现了Lock接口。

悲观锁 vs 乐观锁?CAS(乐观锁实现的基础)实现及存在的问题?

悲观锁和乐观锁是一种宏观的分类方式,并不是特指某个锁,而是在并发情况下两种不同的策略。划分依据:线程要不要锁住同步资源,锁住同步资源失败后,线程要不要阻塞(不阻塞-自旋锁)。

应用场景

简言之,悲观锁阻塞事务,乐观锁回滚重试

乐观锁实现方式

CAS会产生哪些问题?

synchronized锁升级(多个资源竞争同步资源):偏向锁 → 轻量级锁 → 重量级锁

划分依据:多个线程竞争同步资源的流程细节不同

这四种锁是指锁的状态,专门针对synchronized的。在介绍这四种锁状态之前还需要介绍一些额外的知识。

首先为什么Synchronized能实现线程同步?在此之前,需要了解两个重要的概念:“Java对象头”、“Monitor”。

为什么会出现synchronized三种锁?

“阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长”。这种方式就是synchronized最初实现同步的方式,这就是JDK 6之前synchronized效率低的原因。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”,JDK 6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。

三种锁的区别和总结:

ps:同步代码块(资源):即有synchronized修饰符修饰的语句块,被该关键词修饰的语句块,将加上内置锁,实现同步。

公平锁 vs 非公平锁?

划分依据:多个线程竞争锁时要不要排队

public ReentrantLock() {
   sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
   sync = fair ? new FairSync() : new NonfairSync();
}

//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);

//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);

根据参数决定其内部是公平锁还是非公平锁。公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()。源码自行查看,该限制条件作用:主要是判断当前线程是否位于同步队列中的第一个。如果是则返回true,否则返回false。

综上,公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待问题,直接尝试获取锁,所以存在后申请却先获得锁的情况。

可重入锁 vs 非可重入锁? ReentrantLock 的可重入是怎么实现的?

划分依据:一个线程中的多个流程能不能获取同一把锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。

Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

为什么非可重入锁在重复调用同步资源时会出现死锁?

通过重入锁ReentrantLock以及非可重入锁NonReentrantLock为例:首先ReentrantLock和NonReentrantLock都继承父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。

读写锁(共享锁、独享锁)?

划分依据:多个线程能不能共享一把锁,独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

读锁(共享锁):该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。

写锁(互斥锁/独享锁/排他锁):该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。

读写锁是悲观锁策略!因为读写锁并没有在更新前判断值有没有被修改过,而是在加锁前决定应该用读锁还是写锁。

Lock 和 synchronized 有什么区别

ps:可中断锁:可以响应中断的锁,Java并没有提供任何直接中断某线程的方法,只提供了中断机制

Java的中断不能直接终止线程,而是需要被中断的线程自己决定怎么处理。如果线程A持有锁,线程B等待获取该锁。由于线程A持有锁的时间过长,线程B不想继续等待了,我们可以让线程B中断自己或者在别的线程里中断它,这种就是可中断锁

synchronized 和 ReentrantLock 区别是什么?如何选择呢?

可重入锁 ReentrantLock 是 Lock 最常见的实现,与 synchronized 一样可重入,不过它增加了一些高级功能:

一般优先考虑使用 synchronized

巨人的肩膀:

https://zhuanlan.zhihu.com/p/71156910#ref_1
https://zhuanlan.zhihu.com/p/50098743

上一篇 下一篇

猜你喜欢

热点阅读