Java中的Synchronized,你了解多少?

简介: Java中的Synchronized,你了解多少?

一:Synchronzied的底层原理是什么?

Synchronized 是由JVM实现的一种实现互斥同步的一种方式,如果你查看被Synchronized修饰过的程序块编译后的字节码,会发现,被Synchronized修饰过的程序块,在编译前后被编译器生成了monitorenter和monitorexit两个字节码指令。

在虚拟机执行到monitorenter指令时,首先要尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器+1;

当执行monitorexit 指令时将锁计数器-1;当计数器为0时,锁就被释放了。

如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。

Java中Synchronize 通过在对象头设置标记,达到了获取锁和释放锁的目的。

二:“锁”的本质是什么?

锁”的本质其实是 monitorenter 和 monitorexit 字节码指令的一个Reference类型的参数,即要锁定和解锁的对象。我们知道,使用Synchronized 可以修饰不同的对象,因此,对应的对象锁可以这么确定。说明加解锁对象为该对象

1.如果 Synchronized 明确指定了锁对象,比如 Synchronized(变量名)、Synchronized(this)等

2.如果没有明确指定:若 Synchronized 修饰的方法为非静态方法, 表示此方法对应的对象为锁对象;

                                若 Synchronized 修饰的方法为静态方法, 则表示此方法对应的类对象为锁对象。

注意:当一个对象被锁住时,对象里面所有用 Synchronized 修饰的方法

三:什么是可重入性?为什么Synchronized是可重入锁?

可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况。比如一个类中的同步方法调用另一个同步方法,假如Synchronized 不支持重入,进入method2方法时当前线程获得锁,method2方法里面执行method1 时当前线程又要去尝试获取锁,这时如果不支持重入,"它就要等释放,把自己阻塞,导致自己锁死自己。


对Synchronized来说,可重入性是显而易见的,刚才提到,在执行monitorenter 指令时,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取)

其实本质上就通过这种方式实现了可重入性。

四:JVM对Java的原生锁做了哪些优化?

在Java66之前,Monitor的实现完全依赖底层操作系统的互斥锁来实现,由于Java层面的线程与操作系统的原生线程有映射关系,如果要将一个线程进行阻塞在Java6之前,或唤起都需要操作系统的协助,这就需要从用户态切换到内核态来执行,这种切换代价十分昂贵,很耗处理器时间,现代IDK中做了大量的优化。

一种优化是使用自旋锁,即在把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时就无需再让线程执行阻塞操作。避免了用户态到内核态的切换。

现代JDK中还提供了三种不同的Monitor实现,也就是三种不同的锁:

1.偏向锁(BiasedLocking)

2.轻量级锁

3.重量级锁

这三种锁使得JDK得以优化Synchronized的运行,当M检测到不同的竞争状况时,会自动切换到适合的锁实现,这就是锁的升级、降级。

1.当没有竞争出现时,默认会使用JVM会利用CAS操作,在对象头上的MarkWord部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁,因为在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。

2.如果有另一线程试图锁定某个被偏斜过的对象,JVM就撤销偏斜锁,切换到轻量级锁实现。

3.轻量级锁依赖CAS操作MarkWord来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。

五:Synchronized是非公平锁吗?为什么?

非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。

六:什么是锁消除和锁粗化

锁消除:指虚拟机即时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。主要根据逃逸分析。程序员怎么会在明知道不存在数据竞争的情况下使用同步呢?很多不是程序员自己加入的。

锁粗化:原则上,同步块的作用范围要尽量小,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作在循环体内,频繁地进行互斥同步操作也会导致不必要的性能损耗。锁粗化就是增大锁的作用域。

七:为什么synchronized是悲观锁,那么与之相反的乐观锁又是什么,CAS又是什么

Synchronized显然是一个悲观锁,因为它的并发策略是悲观的:不管是否会产生竞争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。

随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略。先进行操作,如果没有其他线程征用数据,那操作就成功了;如果共享数据有征用,产生了冲突,那就再进行其他的补偿措施。

这种乐观的并发策略的许多实现不需要线程挂起,所以被称为非阻塞同步。


乐观锁的核心算法是CAS(Compareandswap,比较并交换),它涉及到三个操作数:内存值、预期值、新值。

当且仅当预期值和内存值相等时才将内存值修改为新值。

这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。


CAS具有原子性,它的原子性由CPU硬件指令实现保证,即使用INI调用Native方法调用由C++编写的硬件级别指令,JDK中提供了Unsafe类执行这些操作

八:乐观锁一定是好的吗?

乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能,但它也有缺点:

1.乐观锁只能保证一个共享变量的原子操作。如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。

2.长时间自旋可能导致开销大。假如CAS长时间不成功而一直自旋,会给CPU带来很大的开销。

3.ABA问题。

CAS的核心思想是通过比对内存值与预期值是否一样而判新内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是A,后来被一条线程改为B,最后又被改成了A,则CAS认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影响很大。

解决的思路是引入版本号,每次变是更新都把版本号加一。

相关文章
|
2月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
66 5
|
2月前
|
存储 Oracle 安全
揭秘Java并发核心:深入Hotspot源码腹地,彻底剖析Synchronized关键字的锁机制与实现奥秘!
【8月更文挑战第4天】在Java并发世界里,`Synchronized`如同导航明灯,确保多线程环境下的代码安全执行。它通过修饰方法或代码块实现独占访问。在Hotspot JVM中,`Synchronized`依靠对象监视器(Object Monitor)机制实现,利用对象头的Mark Word管理锁状态。
45 1
|
2月前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
44 0
|
4天前
|
Java
让星星⭐月亮告诉你,Java synchronized(*.class) synchronized 方法 synchronized(this)分析
本文通过Java代码示例,介绍了`synchronized`关键字在类和实例方法上的使用。总结了三种情况:1) 类级别的锁,多个实例对象在同一时刻只能有一个获取锁;2) 实例方法级别的锁,多个实例对象可以同时执行;3) 同一实例对象的多个线程,同一时刻只能有一个线程执行同步方法。
6 1
|
7天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
15 2
|
3天前
|
安全 Java 开发者
java的synchronized有几种加锁方式
Java的 `synchronized`通过上述三种加锁方式,为开发者提供了从粗粒度到细粒度的并发控制能力,满足了不同场景下的线程安全需求。合理选择加锁方式对于提升程序的并发性能和正确性至关重要,开发者应根据实际应用场景的特性和性能要求来决定使用哪种加锁策略。
7 0
|
1月前
|
存储 安全 Java
Java并发编程之深入理解Synchronized关键字
在Java的并发编程领域,synchronized关键字扮演着守护者的角色。它确保了多个线程访问共享资源时的同步性和安全性。本文将通过浅显易懂的语言和实例,带你一步步了解synchronized的神秘面纱,从基本使用到底层原理,再到它的优化技巧,让你在编写高效安全的多线程代码时更加得心应手。
|
1月前
|
缓存 Java 编译器
JAVA并发编程synchronized全能王的原理
本文详细介绍了Java并发编程中的三大特性:原子性、可见性和有序性,并探讨了多线程环境下可能出现的安全问题。文章通过示例解释了指令重排、可见性及原子性问题,并介绍了`synchronized`如何全面解决这些问题。最后,通过一个多窗口售票示例展示了`synchronized`的具体应用。
|
2月前
|
Java
不懂synchronized?那你可能错过了Java的“半壁江山”!
不懂synchronized?那你可能错过了Java的“半壁江山”!
56 6
|
2月前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
82 5