描述一下synchrnoized和reentrantlock的底层实现及重入的底层原理
synchrnoized (CAS)
是通过1.8JVM虚拟机的ho+spot实现下的
compareAndSwapInt对应的C和C++的Unsafe_CompareAndSwapObject
下的Unsafe_CompareAndSwapInt函数。
cmpxchg=compare and exchange
最后追踪到atomic_linux_x86.inIine.hpp。
调用了一条Lock_IF_MP(mp) cmpxchgl的汇编指令表示如果在多线程下需要先Lock这条指令,
最终实现:cmpxchg = cas修改变量值
lock cmpxchg指令
硬件:lock指令在执行后面指令的时候锁定一个北桥信号
(不采用锁总线的方式)
CAS的ABA问题如何解决
CAS(compare and swap)比较并且交换。
在读取一个内存值并进行计算后再次与原先取到的内存值进行比较如果内存值一致则将计算结果赋值,如果不相等则重复读取计算并比较操作。也就是自旋锁,也叫无锁。
ABA问题,A在读取内存值并进行计算时,B线程曾对该内存值进行修改且赋予的值与A线程读取时的值相等导致A线程认为是同一个值并赋予计算结果的问题。
解决方法:在每次读取值时添加一个版本号即可。
谈一下AQS,为什么AQS的底层是CAS+volatile
谈一下你对volatile的理解
volatile关键字能让内存模型中的变量变为可见性,并且能够通过内存屏障防止CPU的指令重排。
CPU的指令重排序是指在单线程的情况下不管先执行那条指令都能得到最终一致性的结果,因此为了优化性能便不等待某一指令执行完成。而是先执行下一指令从而使得性能得到提升。但是在多线程情况下如果进行指令重排那么就会出现最终一致性不能保证的问题。
volatile的可见性和禁止指令重排序是如果实现的
volatile的禁止指令重排和可见性都是通过JVM提供的内存屏障保证的。
调用底层 lock; addl汇编指令锁定一个空值,从而实现了可见性和禁止指令重排。
Lock用于在多处理器中执行指令时对共享内存的独占使用,它的作用是能够将当前处理器对应缓存的内存刷新到内存,并使其它处理器对应的缓存失效,另外还提供了有序的指令无法越过这个内存屏障的作用。
描述一下对象的创建过程
首先通过new #2 在堆内存中申请一块根据数据类型大小的空间,
在这个过程中m的会有一个半初始化的过程这个时候他的默认值为0
只有当调用了invokespecial #3 <T.>构造方法后才将T变量值为8赋予
并执行astore_1与T建立连接。
对象在内存中的布局
markword对象头占据8个字节。储存了锁信息,GC标记信息,hashCode
klass pointer 类指向 默认压缩后是4个字节,未压缩是8个字节
成员变量占据数据类型对应字节。
padding为确保在64位虚拟机中都是能被8整除的字节,使其填充相应的字节以对应64位虚拟机。
DCL单例为什么要加volatile
在多线程情况下必须要加volatile,
否则会出现A线程由于CPU指令重排时的半初始化对象会被B线程获取到的情况,导致不能保证最终一致性。
解释一下锁的四种状态
聊聊你对as-if-serial和happens-before的语义的理解
as-if-serial指的是,不管CPU如何重排序,单线程执行结果不会改变。
happens-before指的是JVM虚拟机规定的重排序必须遵守的八种规则。
你了解ThreadLocal吗?你知道ThreadLocal中如何解决内存泄漏问题吗?
每一个Thread对象均包含有一个ThreadLocalMap类型的成员变量threadLocals,它储存本线程中所有ThreadLocal对象及其对应的值。
ThreadLocalMap由一个个Entry对象构成
Entry继承自WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成,由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。
当执行set方法时,ThreadLocal首先获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象未key,将值储存金ThreadLocalMap对象中。
get方法执行过程类似,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。在以ThreadLocal对象未key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多线程访问容器的互斥性。
使用场景:
(1)在进行对象跨层传递的时候,使用ThreadLocal可以避免多长传递,打破层次间的约束。
(2)线程间数据隔离
(3)进行事务操作,用于储存线程事务信息。
(4)数据库连接,Session会话管理。
spring框架在事务开始时会给当前线程绑定一个JDBC Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性,spring框架就是使用ThreadLocal来实现这个隔离的。
自旋锁一定比重量级锁效率高吗?
不一定,在明知道会有多线程竞争的情况下,偏向锁肯定会涉及锁撤销,这时候直接使用自旋锁效率会高一些。
JVM启动过程,会有很多线程竞争(明确) ,所以默认情况启动时不打开偏向锁,过一段时间在打开。(4秒)
偏向锁-markword上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否是同一个,所以,偏向锁,偏向加锁的第一个线程,hashCode备份在线程栈上,线程销毁,锁降级为无锁。
有争用-锁升级为轻量级锁-每个线程有自己的LockRecord在自己的线程栈上,用CAS去争用markword的LR的指针,指针指向那个线程的LR,那个线程就拥有锁。
自旋超过10次,升级为重量级锁-如果太多线程自旋CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU)-XX:PreBlockSpin
用户态与内核态
内核态是指操作系统与硬件之间的联系;
用户态是指应用的操作,用户态不能直接访问硬件,比较经由内存态处理
但是内存态需要消耗大量资源,所以在尽量在用户态解决问题。
对象怎么分配?
当一个对象创建出来的时候会首先分配到栈,如果不能分配在栈上那么会根据它的大小来考虑是否分配到老年代,如果不大那么就会分配到线程的本地缓存TLAB里,他会生成给每一个线程一个独立的内存空间,只有当内存空间满时才会去申请新生代的内存空间