java两年开发经验(java开发可以干几年)
导语:从事多年java开发,请说说synchronized的底层原理,内心有点慌
synchronized是由一对monitorenter和monitorexit指令来实现同步的,在jdk1.6之前,monitor的实现是依靠操作系统内部的互斥锁来实现的,所以需要进行用户态和内核态的切换,所以此时的同步操作是一个重量级的操作,性能很低。但是,jdk1.6带来了新的变化,提供了三种monitor的实现方式,分别是偏向锁,轻量级锁和重量级锁,即锁会先从偏向锁再根据情况逐步升级到轻量级锁和重量级锁-—这就是锁升级
jdk1.6之后JVM对synchronized进行了优化,经历三个的过程:
第一:在锁对象的对象头里面有一个threadid字段,默认情况下为空。当第一次有线程访问时,则将该threadid设置为当前的线程id,我们称为让其获取偏向锁。当线程执行结束,则重新将threadid设置为空。
第二:如果线程再次进入的时候,会先判断threadid与该线程的id是否一致。如果一致,则可以获取该对象。如果不一致,则发生锁升级,从偏向锁升级为轻量级锁。
第三:轻量级锁的工作模式是通过自旋循环的方式来获取锁,看对方线程是否已经释放了锁。如果执行一定次数之后,还是没有获取到锁,则发生锁升级,从轻量级锁升级为重量级锁。
锁升级初步
总结:使用锁升级的目的是为了减少锁带来的性能消耗。
通过反编译查看字节码,就可以看到相关的指令。源码:就是写了synchronized同步代码块控制线程安全。
// class version 52.0 (52)// access flags 0x21public class SynchronizedTest { // compiled from: SynchronizedTest.java // access flags 0x1 public <init>()V L0 LINENUMBER 8 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN L1 LOCALVARIABLE this LSynchronizedTest; L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x9 public static main([Ljava/lang/String;)V TRYCATCHBLOCK L0 L1 L2 null TRYCATCHBLOCK L2 L3 L2 null L4 LINENUMBER 10 L4 NEW java/lang/Object DUP INVOKESPECIAL java/lang/Object.<init> ()V ASTORE 1 L5 LINENUMBER 11 L5 ALOAD 1 DUP ASTORE 2 MONITORENTER L0 LINENUMBER 13 L0 ALOAD 2 MONITOREXIT L1 GOTO L6 L2 FRAME FULL [[Ljava/lang/String; java/lang/Object java/lang/Object] [java/lang/Throwable] ASTORE 3 ALOAD 2 MONITOREXIT L3 ALOAD 3 ATHROW L6 LINENUMBER 14 L6 FRAME CHOP 1 RETURN L7 LOCALVARIABLE args [Ljava/lang/String; L4 L7 0 LOCALVARIABLE obj Ljava/lang/Object; L5 L7 1 MAXSTACK = 2 MAXLOCALS = 4}
synchronized如何保证可见性的?
首先,我们需要知道可见性原理:两个线程如何保证变量信息的共享可见性?需要经历以下的流程
线程A -> 本地内存A(共享变量副本) -> 主内存(共享变量)
如果有变更,需要将本地内存的变量写到主内存,对方才可以获取到更新。这个是前提知识。那么,synchronized是如何保证可见性的呢?
结论:当获取到锁之后,每次读取都是从主内存读取,当释放锁的时候,都会将本地内存的信息写到主内存,从而实现可见性。
本文内容由小德整理编辑!