0%

Java筑基-:(14)2021.8.25 深入理解并发编程和归纳总结02

一、synchronized 的实现原理

如果在方法里面使用 synchronized(obj) 这种用法,那么 javac 在编译的时候会插入 monitorenter 和 monitorexit 字节码。规则如下:

  • monitorenter 指令是在编译后插入到同步代码开始的位置;而monitorexit是插入到方法结束处和异常处

  • 每个monitorenter 必须要有对应的 monitorexit 配对

  • 任何对象都有一个 monitor 与之关联

示意图如下所示:

在方法里面使用

如果在使用 synchronized doSomething() 这种方式的话,会在字节码上的 flag 上添加 ACC_SYNCHRONIZED 标记,不过底层原理还是 monitor 这套。示意图如下:

synchronized直接修饰方法

一般的 Java 对象头(除了对象体和数据外的部分)会包括 2 个部分,如果是数组对象就会新增数组长度:

  • MarkWord: GC 信息、 hashCode、锁信息

  • KlassPoint: 对象所属的类

  • ArrayLen(是我自己写的英语,不一定是这个): 数组长度

上述说的锁信息,包括锁状态、是否偏向、锁标志位等,当然,需要注意一点的是,对象头的锁信息不是一成不变的。如:偏向锁时,存放的是偏向线程的 id;轻量级锁的时候,存放了栈中锁记录的指针;重量级锁的时候,存放互斥量(重量级锁)的指针。

二、一线大厂的面试题

1、synchronized 修饰普通方法和静态方法有什么区别?

synchronized 锁一定是和某个对象相关联的,加锁要加在具体地对象上。普通方法修饰当前对象,静态修饰class 对象。思考下:同时存在被 synchronized 修饰的普通方法和 静态方法,这二者能不能同时执行?

1
2
3
4
5
6
7
public synchronized void normalMethod(){

}

public static synchronized void staticMethod(){

}

答案是肯定能够同时执行的,因为这二者压根锁的不是同一个对象,是2个不同的锁,所以肯定可以同时执行。

2、什么是可见性

这涉及到工作内存和主内存。多个线程共享一个变量,如果一个对象更改了这个共享值,其他线程也能立即看到修改值。那如何保证可见性呢?采用 volatile 和 加锁

3、锁的分类

根据不同的场景可以将锁分为多类,如下图所示:

锁的分类

3、CAS的原理

主要利用现代CPU提供的 比较并交换(CAS)指令,这个指令具有原子性。CAS 存在的问题:

  • ABA问题

  • 开销问题

  • 只能适应简单变量问题

4、ReentrantLock 实现原理

进入一次锁,state 就加 1 ,退出一次锁,就减 1 。当然,底层是采用 AQS 实现的

5、AQS 原理

可重入锁、CountDownLatch 、读写锁 等都是使用 AQS

如果需要自己定义一种锁,只需要创建静态内部类,继承 AQS ,这是模板类,只需要实现几个关键方法如 tryAcquire 即可

6、Synchronized 原理

Synchronized(obj) 的形式是基于 monitorenter 和 monitorexit 字段

修饰的是方法的话,在字节码中的Flag 字段中会添加 ACC_SYNCHRONIZED 标记

7、synchronized 和 ReentrantLock 区别

一个是显式锁,一个是关键字,内置所

synchronized 是非公平锁,ReentrantLock 可以公平和非公平

8、锁的优化

偏向锁

轻量级锁

锁消除(与后续的逃逸分析联系比较紧密)

锁粗化

逃逸分析

9、volatile 在 DCL上的作用

new 一个对象会有3个步骤:

  • 分配内存

  • 初始化空间

  • 赋值给引用

但是会有指令重排序,上述的 3 个步骤不一定按顺序完成,如果分配内存、赋值引用执行了,但是初始化空间还没完成,这时候双重检查的单例方式可能会使用初始化一半的对象,使用就会报错。

所以需要 volatile 关键字修饰

10、如何退出一个线程

采用线程的 interrupt 方法,利用中断机制来退出线程。

11、线程池原理

为什么要用线程池:

  • 降低资源消耗

  • 提高响应速度

  • 提升线程的可管理性

再讲一讲线程池里面各个参数的含义。后续的线程拒绝策略,系统提供的4种拒绝策略

12、t1、t2、t3 三个线程,如何保证按顺序执行

采用 join 方法嘛

三、了解synchronized升级过程

通过CAS操作来加锁和解锁。采用自旋锁方式获取锁,万一其他线程很快释放锁呢。避免阻塞。

oracle 发现在很多情况下,1个锁在很多情况下都是同一个线程来获取的,所以这时候 CAS 操作都不想做了,这时候就产生了 偏向锁。(不过markword的字段更改的时候还是得用CAS操作的)

synchronized 整个的升级过程如下图所示:

synchronized升级过程

偏向锁和轻量级锁的对象头是完全不一样的,所以在撤销偏向锁改为轻量级锁的时候,需要替换 markword 。

但是撤销偏向锁的时候,会 STW ,撤销偏向锁的时候,需要修改线程 1 的堆栈内容的。markword 会存放在 线程 1 的堆栈里面,线程 2 要跨线程把线程1的堆栈的数据改过来。所以需要 Stop The World

谢谢你的鼓励