深入显出一篇能懂Java锁机制,Synchronized和ReentrantLock
Posted 陆海潘江小C
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入显出一篇能懂Java锁机制,Synchronized和ReentrantLock相关的知识,希望对你有一定的参考价值。
目录
五、synchronized和ReentrantLock的区别
本篇内容共 2303 字,5234字符,阅读需要 6分钟。
在Java中,多线程并发的应用场景下,同步性和安全性在设计中尤为重要,这也是面试当中时常提及的问题。在项目中,如果有使用多线程的经历,通常会应用到Java中的锁来保证线程安全和数据的一致性。
到目前,Java中的锁机制可以说很全面且强大了。同步、共享、互斥等功能的实现,我们有了许多选择,包括实现共享对象访问的机制:synchronized和volatile,还有提供高级特性的ReentrantLock机制。
这篇文章就简单记录一下synchronized 和 ReentrantLock 的原理应用和区别,在以后的使用选择中有着更加明确的依据。
一、synchronized关键字特性
sychronized关键字保证多个线程之间访问资源的同步性,被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
synchronized关键字是原生语法层面的互斥,在JVM层面实现,是一种重量级锁,因为在互斥状态下,没有获取到锁的线程会被挂起阻塞,而挂起线程和恢复线程的操作都需要系统转入内核态中完成。
在JDK1.6官方对synchronized在JVM层面进行大量的优化,引入了自旋锁、偏向锁、适应性自旋锁、锁消除、锁粗化、轻量级锁等技术来减少锁操作的开销。
另外,上面提到的使用synchronized修饰方法和代码块,主要有三种情况:
- 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。
二、synchronized应用:双重校验锁实现单例模式
以下实现的单例模式是线程安全的,使用synchronized关键字将类对象加锁。
/**
* 双重校验锁实现单例模式(线程安全)
* @author Charzous
* @date 2021-07-03 下午 06:56
*
*/
public class singleton {
private static volatile singleton singletonInstance;
private singleton(){
}
public static singleton getSingletonInstance(){
// 先判断对象是否已经实例过,没有实例化过才进入加锁代码块
if (singletonInstance==null){
// 类对象加锁
synchronized (singleton.class){
if (singletonInstance==null){
singletonInstance=new singleton();
}
}
}
return singletonInstance;
}
}
其中,volatile关键字是必要的,因为 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。
在多线程的情况下,volatile关键字保证了可见性,禁止 JVM 的指令重排,保证在多线程环境下也能正常运行 。
三、显式锁:Lock和ReentrantLock
1、Lock接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
在JDK1.5,开始提供Lock接口,从上面的源码可以发现,与synchronized这种内部加锁机制不同,Lock提供了无条件、定时的、可轮询、可中断的锁操作,加锁和解锁的操作都是显式进行的。
2、ReentrantLock
ReentrantLock是JDK1.5提供的互斥锁API。它实现了Lock接口,提供了跟synchronized相同的互斥和内存可见。
ReentrantLock支持Lock接口定义的所有获取锁的模式,因此对于处理不可用的锁提供了更多的灵活性。ReentrantLock锁机制的本质跟内部锁十分相似,它的优势就体现在:
- ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。
- 简化了代码,更好地与异常处理机制相结合,某些情况下,提供了更好的性能。
当然,使用ReentrantLock的时候,需要知道它的特性以及带来的问题。
上面说到,Lock的加锁和解锁都是显式操作,因此这种锁比内部锁更加复杂,锁必须在finally块中释放。另外,如果在加锁的代码块之外抛出了异常,锁将永远不会被释放。这些问题都是需要警惕的,出现错误很难找出程序的发生点。
class X {
ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
assert !lock.isHeldByCurrentThread();
lock.lock();
try {
// ... method body
} finally {
lock.unlock();
}
}
}
四、锁的公平性
ReentrantLock的构造函数中提供了两种锁的选择:非公平锁(默认)和公平锁。
部分源码如下:
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁则是保证线程按照先来先服务的请求顺序,如果锁已经被其他线程占有,或者已经存在线程在等待锁了,新请求线程会加入等待队列;而非公平锁则是允许抢占的方式,线程只有在锁被占用时才会等待,一旦可以获得锁,不会让给等待队列中的其他线程。
在实际场景中,非公平锁的性能明显高于公平锁。
如上图所示,在测试中,基于ReentrantLock实现的HashMap,即ConcurrentHashMap,表现出的性能不错,而公平锁表现最差。
这是因为:
在竞争激烈的情况下,抢占机制可以使得线程获取资源的效率更高。非公平锁的合适使用场景持有锁的时间段,或者请求锁的平均时间间隔比较短。
假设线程A持有一个锁,线程B请求该锁,此时线程B会被挂起,当A释放锁之后B才开始。同时,存在线程C请求该锁,C有机会得到这个锁,而且可能在B唤醒前C已经释放该锁。因此,这种情况下,C更早获取锁,B也在唤醒时得到锁,这是吞吐量得到了提高。
相反,对于持有锁时间长,请求锁平均时间间隔长的,公平锁就更有优势。
五、synchronized和ReentrantLock的区别
学习到这里,我们已经初步了解synchronized关键字和ReentrantLock的原理和一些应用场景,在分析过程中也可以发现他们相似和相异之处,这里简单总结一下:
- 两者都是可重入锁。
- synchronized是关键字,属于JVM层面,而ReenTrantLock是锁API。
- ReenTrantLock 比 synchronized 增加了一些高级功能,可见上文。
- ReentrantLock可实现选择性锁机制。
学习Java开发,多线程等知识,最近我也在跟着这一套 《Java 工程师学习成长知识图谱》进行体系的学习,是CSDN官方推出的,质量很不错!
其中包含了Java专业体系结构完整详细,推荐给大家学习使用,有兴趣可以扫码查看,最近我也在学习当中,当然,我的文章会记录学习,欢迎大家阅读,比如我的专栏《Java宝藏》、《Socket网络编程》。
展开就是这样的,尺寸870mm*560mm排版好看,内容很充实。推荐给有需要的伙伴,一起来学习Java!
如果觉得不错欢迎“一键三连”哦,点赞收藏关注,评论提问建议,欢迎交流学习!一起加油进步,我们下篇见!
本篇内容首发我的CSDN博客:https://csdn-czh.blog.csdn.net/article/details/118438799
以上是关于深入显出一篇能懂Java锁机制,Synchronized和ReentrantLock的主要内容,如果未能解决你的问题,请参考以下文章
深入研究 Java Synchronize 和 Lock 的区别与用法
[转] 深入研究 Java Synchronize 和 Lock 的区别与用法