1.syncronized锁升级的过程讲一下?
2.JVM对Synchornized的优化?
3.介绍一下AQS
4.Threadlocal作用,原理,具体里面存的key value是啥,会有什么问题,如何解决?
5.悲观锁和乐观锁的区别?
6.Java中想实现一个乐观锁,都有哪些方式?
7.CAS 有什么缺点?
8.为什么不能所有的锁都用CAS?
9.voliatle关键字有什么作用?
10.指令重排序的原理是什么?
只能升级不能降级。
可以看到在 Java 虚拟机中,对象分为三块区域,其中的对象头又包含 Mark Word(标记字段) 和 Class Pointer(类型指针) 两部分(其中的数组长度是针对数组来说的)。
无锁,偏向锁,轻量级锁,重量级锁。
随着对锁的请求线程数的竞争激烈程度不断升级。
无锁:当无线程申请获取锁时,处于无锁状态。
偏向锁针对资源总被一个线程使用的情况:当有线程申请获取锁,锁类型被标记为偏向锁,并将当前线程ID记录在Mark Word。之后线程再次进入是,利用CAS比较记录id与线程id,如果相同直接进入资源,否则申请锁,修改Mark Word。
轻量级锁:轻量级锁是为了在线程近乎交替执行同步代码时提高性能。当线程 A 与 线程 B 同时抢占锁对象时,偏向锁会被撤销并将锁升级为轻量级锁。轻量级锁当一个线程A抢占了资源,B会不断循环重试,直至获取到锁。(有点复杂,将对象中的Mark word利用CAS写入线程的Lock Record里面,再将对象中的Mark word 指向Lock Record,这样才算获取到锁。)
重量级锁:多个线程竞争时,升级为重量级锁,不再使用自旋,而是直接阻塞线程(线程会被操作系统调度然后挂起,这可以节约CPU资源。)。
锁膨胀(锁升级),锁消除(JJVM 虚拟机如果检测不到某段代码被共享和竞争的可能性就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。),锁粗化(将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。),自适应自旋锁(自适应自旋锁通过监控锁的使用情况,动态地调整自旋的次数或者自旋的策略,以更好地适应当前程序的运行状况。)。
作用:ThreadLocal对象可以提供线程局部变量,每个线程Thread拥有一份自己的**副本变量,多个线程互不干扰。**在同一个线程里的一系列操作可以减少参数传递,降低耦合度。
原理:每个线程都有自己的 ThreadLocalMap,这个映射表存储了线程的局部变量,其中键是 ThreadLocal 对象的弱引用,值为我们保存的值。
问题:内存泄漏。线程结束,ThreadLocalMap也随之销毁,但是ThreadLocal对象本身并没有立即被垃圾回收,直到没有其他引用指向它为止。因此,实际应用中需要在使用完ThreadLocal变量后调用remove()方法释放资源。
悲观锁就是认为线程之间的竞争总是在发生,因此,当线程尝试对某种资源进行操作时会直接上锁,直至操作完成才释放锁。
乐观锁则认为线程之间的竞争是极少发生的,因此,当线程尝试对于某种资源进行操作时并不上锁,而是将比较-替换这两个动作作为一个原子操作尝试去修改内存,如果失败则说明有其他线程已经修改过了,则遵循相应的机制进行重试。
CAS:利用比较-替换的原子操作,实现线程安全。
版本号控制:每当一个线程要修改数据时,都会先读取当前的版本号或时间戳,并将其保存下来。线程完成修改后,会再次读取当前的版本号或时间戳,如果发现已经变化,则说明有其他线程对数据进行了修改,此时需要回滚并重试。
ABA问题:加入版本号之类的。如Java中有AtomicStampedReference来解决这个问题。
自旋CAS的方式,长时间不成功会不断循环重试,会浪费大量的CPU资源。
只能保证一个共享变量的原子操作:多个可以通过AtomicReference来处理或者使用锁synchronized实现。
CAS是基于循环重试机制的。如果CAS操作未能成功,线程会一直自旋重试,占用CPU资源。当高并发的时候,会对CPU资源造成极大地浪费。
1.保证变量对所有线程的可见性。
2.通过内存屏障禁止指令重排。