目录
问题一:Synchronized用过吗,其原理是什么?
问题二 : 你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁 ?
问题三:什么是可重入性,为什么说 Synchronized 是可重入锁?
问题四:JVM 对 Java 的原生锁做了哪些优化?
问题五:为什么说 Synchronized 是非公平锁?
问题六:什么是锁消除和锁粗化?
问题七:为什么说 Synchronized 是一个悲观锁?
问题八:跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同?
问题一:Synchronized用过吗,其原理是什么?
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 是可重入锁?
可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况比如下面的伪代码,一个类中的同步方法调用另一个同步方法,假如Synchronized 不支持重入,进入 method2 方法时当前线程获得锁method2 方法里面执行 method1 时当前线程又要去尝试获取锁,这时如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己
Synchronized 来说,可重入性是显而易见的,刚才提到,在执行对monitorenter 指令时,如果这个对象没有锁定,或者当前线程已经拥
有了这个对象的锁(而不是已拥有了锁则不能继续获取 ),就把锁的计数器 +1,其实本质上就通过这种方式实现了可重入性
问题四:JVM 对 Java 的原生锁做了哪些优化?
在 Java 6 之前,Monitor t的实现完全依赖底层操作系统的互斥锁来实现,也就是我们刚才在问题二中所阐述的获取/释放锁的逻辑由于 Java 层面的线程与操作系统的原生线程有映射关系,如果要将一个线程进行阻塞或唤起都需要操作系统的协助,这就需要从用户态切换到内核态来执行,这种切换代价十分昂贵,很耗处理器时间,现代 JDK中做了大量的优化。
一种优化是使用自旋锁,即在把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时就无需再让线程执行阻塞操作,避免了用户态到内核态的切换现代 JDK 中还提供了三种不同的 Monitor 实现,也就是三种不同的锁:偏向锁(偏置锁)
轻量级锁
重量级锁
这三种锁使得JDK 得以优化 Synchronized 的运行,当JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这就是锁的升级降级当没有竟争出现时,默认会使用偏向锁
JVM 会利用 CAS 操作,在对象头上的 Mark Word 部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁,因为在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定使用偏斜锁可以降低无竞争开销。
如果有另一线程试图锁定某个被偏斜过的对象,JVM就撤销偏斜 锁切换到轻量级锁实现。
轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功就使用普通的轻量级锁 ;否则,进一步升级为重量级锁,
问题五:为什么说 Synchronized 是非公平锁?
非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竟争到锁这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象
问题六:什么是锁消除和锁粗化?
锁消除 : 指虚拟机即时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。主要根据逃逸分析程序员怎么会在明知道不存在数据竟争的情况下使用同步呢 ? 很多不是程序员自己加入的。
锁粗化 :原则上,同步块的作用范围要尽量小。但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作在循环体内,频繁地进行互斥同步操作也会导致不必要的性能损耗。锁粗化就是增大锁的作用域。
问题七:为什么说 Synchronized 是一个悲观锁?
Synchronized 显然是一个悲观锁,因为它的并发策略是悲观的 :不管是否会产生竟争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作,随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略先进行操作,如果没有其他线程征用数据,那操作就成功了
问题八:跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同?
其实,锁的实现原理基本是为了达到一个目的让所有的线程都能看到某种标记。
Synchronized 通过在对象头中设置标记实现了这一目的,是一种 JVM原生的锁实现方式,而 ReentrantLock 以及所有的基于 Lock 接口的实现类,都是通过用一个 volitile 修饰的 int 型变量,并保证每个线程都能拥有对该 int 的可见性和原子修改,其本质是基于所谓的 AQS框架