【并发专题】synchronized 连环炮
本问题对应的项目链接:高频题回答模版训练
PS:synchronized 这个关键字,和 Volitale 类似,一般来说,可以问的问题不多,大概就是问一下作用,以及底层大概怎么实现的
1、简单说一下 synchronized 的作用?
2、synchronized 加在方法上和加载在对象上,有什么不同?
3、讲一下 synchronized 的底层是怎么实现的?(PS:加在实例上和加在方法上,实现不一样,都要说明,一个从字节码角度说,一个从对象头角度说)
4、在 Java 中,synchronized 属于重量级锁,为了让 synchronized 效率更快,JDK做了哪些升级?
共以下 3 个回答
1、简单说一下 synchronized 的作用?
当一个对象加上synchronized关键词后,任何线程对他的读写都会被加上锁,只有当线程执行结束将锁解除,其他线程才能操作该对象。
2、synchronized 加在方法上和加载在对象上,有什么不同?
对于普通方法,锁的是当前的实例对象;对于静态方法,锁的是当前类的 Class 对象;对于同步代码块,锁的是 synchronized 括号里配置的对象。
3、讲一下 synchronized 的底层是怎么实现的?
4、在 Java 中,synchronized 属于重量级锁,为了让 synchronized 效率更快,JDK做了哪些升级?
JDK首先先尝试给synchronized加偏向锁,当出现资源竞争的时候再慢慢将锁升级,从偏向锁升级为轻量级锁,再升级为重量级锁。不过在升级为重量级锁之前会先尝试生成自旋锁,如果锁很少自旋成功,则才会升级为重量级锁。
1.synchronized是用于同步的关键字,可以修饰在类,方法上,被修饰的代码块称为临界区,线程在进入临界区前需要尝试获取锁,在离开临界区后释放锁,只有持有锁才能执行临界区代码,否则需要阻塞,直到获取锁;
2.如果是普通方法,锁的是当前实例对象,如果是静态方法,锁的是类对象,如果是代码块,锁的是括号里的对象;
3.方法的同步是通过方法的常量池中ACC_SYNCHRONIZED标志,线程在执行方法前会先检查,如果是同步方法,会尝试获取监视器锁,执行结束释放锁,如果有异常会在异常抛出前自动释放; 对于同步代码块,jvm在临界区的前和后分别加入monitorenter和monitorexit指令,在进入临界区前需要获取锁,离开后释放,同时每个锁对象持有一个计数器,当数字为0的时候所有线程有机会获取锁,当不为0的时候只有已经获取锁的线程才能再次获取锁,即可重入锁;
4.从jdk1.6开始,jvm对内部锁进行了优化,主要包括锁消除,自旋锁,自适应自旋锁,偏向锁,锁粗化。
在编译的时候,jit编译器可以通过逃逸分析来判断同步块使用的锁对象是否被其他线程访问,如果没有其他线程竞争,那么会撤销锁。
自旋锁就是通过cas进行锁获取,如果一定次数拿不到锁,根据经验再自旋下去是浪费,于是线程阻塞,这就是自适应自旋锁。
偏向锁指同一个线程可以重复获取同一把锁。
锁粗化指在编译时当JIT发现一系列连续的操作都对同一个对象反复加锁和解锁,甚至加锁操作出现在循环体中的时候,会将加锁同步的范围扩散(粗化)到整个操作序列的外部。
1、简单说一下 synchronized 的作用?
synchronized 主要用来实现同步,被 synchronized 修饰的代码块或方法称为临界区代码,在进入临界区之前,线程会尝试获取锁,如果锁可用,则线程会获取锁并进入临界区,执行相应的代码。否则线程将被阻塞,直到获取到锁为止。
2、synchronized 加在方法上和加载在对象上,有什么不同?
对于普通同步方法,锁的是当前的实例对象;对于静态同步方法,锁的是当前类的 Class 对象;对于同步代码块,锁的是 synchronized 括号里配置的对象。
3、讲一下 synchronized 的底层是怎么实现的?(PS:加在实例上和加在方法上,实现不一样,都要说明,一个从字节码角度说,一个从对象头角度说)
对于方法级的同步来说,JVM 利用方法常量池中的 ACC_SYNCHRONIZED 标志来区分一个方法是否为同步方法。当线程访问同步方法时,首先会检查这个标志是否被设置,如果是,则需要先获得监视器锁,然后开始执行方法,执行结束会释放监视器锁。
对于同步代码块来说,JVM 是在编译后代码块的开始和结束处,插入 monitorenter 和 monitorexit 指令来实现的。任何对象都会对应一个 monitor,线程执行到 monitorenter 时会尝试获取对象所对应的 monitor 的所有权,也就是加锁。执行到 monitorexit 时会释放 monitor 的所有权,也就是解锁。
4、在 Java 中,synchronized 属于重量级锁,为了让 synchronized 效率更快,JDK做了哪些升级?
Java 1.6 以后主要引入了偏向锁,轻量级锁和适应性自旋等特性。总体上来说,锁升级的过程为:无锁->偏向锁->轻量级锁->重量级锁。
对于偏向锁来说,首次获取时需要用 CAS 设置 Java 对象头中 mark word 字段的线程 id ,只后持有偏向锁的线程每次进入这个锁相关的同步块时,只需比对一下是否为本线程,如果是则直接获取锁成功。这适用在一个线程反复获取同一个锁的情况,可以提高带有同步但无竞争的程序性能。
偏向锁中发生线程竞争,会升级为轻量级锁。轻量级锁在每次获取时都需要用 CAS 比较并替换对象头中的整个 Mark Wrod 字段,如果 CAS 成功则代表获取锁成功。由于绝大部分的锁在整个生命周期内都不会存在竞争,那么在多线程交替执行同步块的情况下,轻量级锁可以避免重量级锁引起的性能消耗。
轻量级锁释放时若存在其它线程竞争,锁将膨胀为重量级锁,任何没有竞争到锁的线程都会被阻塞,直到被其它线程唤醒。但线程唤醒的过程涉及到操作系统的调用,会有额外的开销。
为了避免这个问题,在线程竞争轻量级锁失败后,膨胀为重量级锁之前线程会尝试适应性自旋,自旋的次数根据前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果这个锁很少自旋成功,那么以后有可能省略掉自旋过程以避免 CPU 资源浪费。