1. Synchronize为什么是重量级的
2. 如何将Synchronized降为轻量级(锁只能够升级不会降级)
3. 什么是CAS
4. CAS的ABA问题如何解决
5. Synchronized与ReenTrantLock的底层实现及重入的底层原理,异同点
6. 锁的四种状态与升级过程
7. 谈一下AQS,AQS的底层为什么是CAS+volatile
8. volatile的理解
9. 描述对象的创建过程(半初始化操作)
10. DCL单例为什么要加volatile
11. Object o = new Object() 在内存中占用多少个字节
12. as-if-serial与happens-before的语义理解
13. ThreadLocal如何解决内存泄露的
14. 锁的分类与在JDK中的应用
15. 自旋锁一定比重量级锁效率高么
16. 打开偏向锁是否效率一定会提升
17. atomic的底层原理
18. 锁重入与锁解锁 —支持重写
1. Synchronized 为什么是重量级锁
- 重量级锁:申请锁资源需要经过操作系统调用,需要经过操作系统内核kernel
3. 什么是CAS
CAS是一种锁的操作,是一种乐观锁。
CAS:compare and swap
CAS有三个参数:CAS(E,V,N) E需要更新的对象,V对象的原值,N需要更换后的值。
操作的时候,只有当E的值与V相等的时候,才会将E的值更新为N,否则就不执行更新操作。
在多线程的情况下,当返回失败的时候会继续调用CAS方法,直到成功。
无锁或者自旋锁。
CAS的问题:
- (1)会产生ABA问题:读取值的时候为A,更新的时候还是A,但是中间A可能改完B,然后B又被改成A。
- 解决方法:加上版本号。AtomicStampedReference/AtomicMarkableReference方法。
- (2)高并发情况一直循环,大量消耗CPU。
在执行compareAndSwapInt方法的时候,调用的是底层的汇编指令 lock cmpxchg 锁定系统总线。
4. CAS的ABA问题如何解决
- 给对象加上版本号,每次比较的时候带上版本号进行比较
5. Synchronized与ReenTrantLock的底层实现及重入的底层原理,异同点
5.1 ReenTrantLock是通过CAS与AQS队列来实现的。
- 1. ReenTrantLock支持公平锁与非公平锁,默认为非公平锁,通过new ReenTrantLock(true)参数为true来实例化公平锁。
- 2. ReenTrantLock通过lock()与unlock()方法来进行加、解锁
- 3. ReenTrantLock是一个可重入锁
- lock方法调用了compareAndSetState(int 0,int 1)方法【非公平锁有这样步骤】,底层是Unsafe类型的compareAndSwapInt方法,CAS自旋(底层是汇编的Lock CMPXCHG指令),
- 如果compareAndSetState方法成功,则获取锁信息,设置独占锁的信息为当前的线程。
- 如果没有获取锁,都会去调用acquire(1)【AQS-AbstractQueueSynchronized】方法(源码在AQS视图里面),要么获取锁,要么加入CHL队列进行等待。
- unlock()方法 调用的是AQS的release方法(源码在AQS视图里面)
- lockInterruptibly()调用的是AQS的AcquireInterruptibly(int arg)方法,响应中断获取锁方式
5.2 Synchronized (JDK1.6版本优化)
JVM级别的锁,通过monitor对象来进行锁操作的。monitorenter和monitorexit
1. Synchronized修饰的对象不同
- 1.1 Synchronized修饰在实例方法的时候 public synchronized void test(){}, 反编译后可以看见方法上提交了ACC_SYNCHRONIZED修饰符,JVM如果扫描到存在ACC_SYNCHRONIZED修饰符会调用Monitor对象,对方法添加锁信息,方法执行结束后会调用monitorexit释放锁。
- 1.2 Synchronized修饰在类成员方法(静态方法)的时候,public synchronized static void test(){} 反编译也会存在ACC_SYNCHRONIZED修饰符,和上面情况一样。
- 1.3 Synchronized修饰在对象上的时候, synchronized(this){},反编译后会在方法前后加上monitorenter与monitorexit指令。
- 1.1.1 monitorenter指令表示获取锁对象的monitor对象,执行monitorenter指令的时候会在monitor对象里面的count进行加一操作,执行monitorexit会对count进行减一操作。
Synchronized锁的信息保存在对象的头信息的Mark Word的后两位里面。结构存在“多线程与高并发(锁)”里面。
差异:
- 1. 底层实现:
- Synchronized是JVM层级的锁,是JAVA的关键字,通过monitor(monitorenter、monitorexit)指令来进行锁的操作。只有在同步代码块中才能够调用wait与notify方法;
- ReenTrantLock是JDK1.5以来提供的API层级的锁。
- 2.是否可以中断
- Synchronized是不可中断的锁类型,除非执行过程中报异常或者正常执行结束。
- ReenTrantLock是可以中断的锁,使用tryAcquire(int arg,TimeOut time)超时中断或者acquireInterruptibly()方法调用interrupt中断。
- 3.是否需要手动释放
- Synchronized是可以在代码执行结束后自己释放锁的,不需要手动释放
- ReenTrantLock的加锁与解锁需要手动控制,通过lock()、unlock()方法在try\finally代码块中执行,如果不手动释放锁会导致死锁产生。
- 4.是否公平锁
- Synchronized是非公平锁
- ReenTrantLock默认是非公平锁,可以在初始化的时候通过构造函数new ReenTrantLock(flag)来初始化锁是否为公平或非公平,true为公平锁,false为非公平锁
- 5. 锁上是否可以绑定条件condition
- Synchronized不能够绑定condition,Synchronized只能够通过Object类的wait()/notify()/notifyAll()方法来随机唤醒一个或者唤醒所有线程;
- ReenTrantLock可以通过Condition类来绑定condition,通过await()/signal()方法来精确唤醒线程
- 6.锁的对象
- Synchronized锁的是对象,锁信息保存在对象头的Mark Word里面
- ReenTrantLock锁的是线程,线程的信息存储在CHL队列的Node节点里面
6. 锁的四种状态与升级过程
状态:
- 1. 无锁
- 2. 偏向锁
- 3. 轻量级锁(自旋锁)
- 4. 重量级锁
锁升级的过程:
- 1. 线程A在进入同步代码块前,先检查Mark Word中的线程ID是否为A的ID,如果为A的ID,这A获取该锁,否则判断这个锁是否为偏向锁。
- 2. 如果这个锁不是偏向锁,则A进行自转,等待锁的释放。
- 3.如果锁为偏向锁,判断该线程是否存在(偏向锁不会主动释放锁),如果不存在,则把线程ID改成A的ID。
- 4. 如果存在,则暂停该线程,并将锁的标志位升级为00即轻量级锁(将Mark Word中信息复制到该线程的栈帧中,同时在Mark Work设置栈帧中的锁记录
- Lock record指针),线程A在外自旋等待锁的释放。
- 5. 当线程A自旋次数到达设定的自旋上限(默认10),线程锁还没有释放,或者A在自旋过程中,又有其它的线程过来竞争这个锁对象(竞争加剧),这个时候会将轻量级锁升级为重量级锁,外部的线程都进入等待队列,不再自旋抢锁,当锁释放后,会唤醒所有的等待线程,让他们重新竞争锁。
- 注:JDK1.6后,轻量级锁升级为重量级锁是JVM控制的,自适应操作
8. volatile的理解
不能够保证程序的原子性,Synchronized可以
- 1)指令重排序
- 2)程序可见性
缓存一致性协议 MESI
- 1. 缓存一致性 volatile修饰
- 1.1 当线程修改一个共享变量后,会立刻将修改的内容刷新到主存中,确保主存中的数据永远是最新的。
- 1.2 当前线程修改某一共享变量后,系统会通知所有使用该变量的线程,该对象状态已经为无效状态了,需要重新去主存中获取最新的内容,再次读取到工作缓存中
9. 描述对象的创建过程(半初始化操作)
int a=8;
- 1)分配内存空间 a=0
- 2)初始化对象 a=8
- 3)将内存空间的地址赋值给对象的引用 a=8
10. DCL单例为什么要加volatile
- 在对象的初始化过程中,如果不加volatile参数,上面的第2与3步可能会发送指令重排序操作,这个时候变量的值就有可能为a=0
12. as-if-serial与happens-before的语义理解
- as-if-serial 不管如何重排序,单线程执行的结果不会改变
- happens-before JVM重排序规则 8种 4种内存屏障 LL SS LS SL
17. atomic的底层实现原理
atomic:底层是通过自旋+CAS来实现的,CAS解决并发原子性操作。
- 1.调用Unsafe类,直接访问对象的内存
- 2.使用getIntVolatile(var1,var2)方法,获取线程间共享变量
- 3.使用CAS尝试机制,进行更新数据
- 4. 使用Atomic是在硬件和寄存器上进行阻塞,而不是代码、线程
do..while..自旋代码在高并发的情况下会加大执行次数,消耗CPU,在低并发的情况下比较有优势,因为不进入内核态,会提高一定的性能。
Unsafe类中的getAndAddInt方法
public final int getAndAddInt(Object var1,long var2,int var4){
int var5;
do{
var5 = this.getIntVolatile(var1,var2);
//使用unsafe的native方法,实现高效的硬件级别CAS
}while(!this.compareAndSwapInt(var1,var2,var5,var4));
return var5;
}
补充:线程基本概念
Synchronized
volatile
AtomicXXX
各种JUC的同步锁
- ReenTrantLock 排它锁、公平锁
- CountDownLatch 门栓 latch.await()阻塞,countDown()为0的时候才会执行后面方法
- CyclicBarrier 栅栏,满多少个线程后,执行CyclicBarrier里面定义的线程
- Phaser 分段执行线程
- ReadWriteLock 读写锁
- ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock(); - Semaphore 限流,限制只允许获取acqurie()成功的线程执行
- Exchanger exchange()交换线程引用
- LockSupport unpark() park() 较灵活的暂停与启动