synchronized 使用 优化 原理

tech2023-09-19  77

一、使用:

// // 1 静态方法上加synchronized 锁对象是class文件 public static synchronized void test1() { System.out.println("使用静态方法加锁。。"); } // 2 普通方法上加synchronized 锁对象是this对象(注意如果对象不是单例,加锁没有意义) public synchronized void test2() { System.out.println("使用普通方法加锁。。"); } // 3 普通方法内加synchronized 锁对象是this对象(注意如果对象不是单例,加锁没有意义) public void test3() { synchronized (Object.class) { System.out.println("方法内部加锁。。"); } }

二、优化:

2.1:锁粗化:

例如:

StringBuffer sb = new StringBuffer(); public void test1() { sb.append(1); sb.append(2); sb.append(3); sb.append(4); } // append 方法源码如下 public synchronized StringBuffer append(int i) { toStringCache = null; super.append(i); return this; }

test1方法 有4次加锁解锁,加锁解锁非常消耗性能, 所以从 java 1.6 以后,上面的test1方法不在是4次加锁解锁,而是优化一次加锁解锁,append方法的锁消除掉,逻辑如下:

public void test1() { synchronized (Object.class) { sb.append(1); // append方法的synchronized锁消除掉 sb.append(2); sb.append(3); sb.append(4); } }

2.2:锁消除:

例如:

public Integer test() { synchronized (new Object()) { int a = 1; System.out.println("锁消除代码"); return a; } }

锁对象不一致,加锁无意义,所以jvm 会去掉synchronized 锁, java 1.6 以后这个优化叫做锁消除

2.3:锁膨胀升级:

java 1.6 后 ,做了优化,底层不在直接使用monitor重量级锁。下面讲原理会分析整个过程(这里只是图片,在下面第三块会分析整个流程)。

三、synchronized原理:

3.1 简介

synchronized: 内置锁、隐式锁、对象锁,言外之意不需要手动加解锁,使用对象加锁。

3.2 jvm执行synchronized

解释上述流程图: synchronized关键字 翻译成命令

public Integer test() { synchronized (xxxx) { int a = 1; System.out.println("代码"); return a; } }

翻译成命令后 变成

public Integer test() { monitorEnter int a = 1; System.out.println("代码"); return a; monitorExit }

当一个线程执行到monitorEnter命令时 , 尝试去抢占对象锁,如果锁已经被占用,那么线程进入等待队列。如果成功获取到锁,那么执行代码逻辑直到执行命令monitorExit,当前线程退出锁,通知其他等待线程竞争锁。

3.2 对象是怎么加锁的呢?

锁状态是被记录在每个对象的对象头(Mark Word)中,下面我们一 起认识一下对象的内存布局:

整个对象结构分为三个部分:对象头,实例数据,对齐填充位。而我们锁主要是靠对象头mark word部分实现,下图是mark word 结构和定义 那么下面是讲述锁的原理(包含上述没有讲解锁升级的过程): 对象加锁主要是依靠对象头的mark word。下面是锁升级过程mark word数据变化。弄清楚了,也就明白了对象是怎么加锁。

无锁:synchronized加锁的代码还没被线程执行过。 mark word数据如下

偏向锁:一旦有线程访问执行synchronized加锁的代码,立马升级为偏向锁, mark word:修改为 偏向锁标志修改为1,最开始25位修改为当前线程id。 偏向锁执行流程:凡是有线程进来,对比mark word 最后两位,01表示无锁或者偏向锁。然后对比倒数第三位,1表示偏向锁,0表示无锁。因此mark word最后三位数组合为 0 01 表示无锁,最开始25位修改为当前线程id,1 01 表示偏向锁,这时候会对比线程id,如果相同那么直接执行同步代码,不相同会尝试去获取一次偏向锁获取到则直接替换threadId,执行同步代码,获取失败则升级为轻量级锁。 轻量级锁:当多个线程执行synchronized加锁的代码,且上一个线程没有执行完毕同步代码块,下一个线程也需要执行同步代码块。这个时候未拿到锁的线程需要等待。这种线程等待时间很短就能获取到锁。通过自旋等待方式。 mark word:修改为 mark word前30位改成lock record指针,最后两位是锁表示00(轻量级锁) lock record解释 :这是每个线程的线程栈开辟的一块空间,这个空间是存储了mark word和一些变量(例如:变量:owner 指向mark word指针)把这个线程栈空间叫做lock record 轻量级执行流程:多个线程检查锁表示位00 ,直接去竞争锁,修改lockrecord指针,如果成功,那么直接执行同步代码,失败线程进入自旋等待。如果自旋一定次数为获取到锁,那么就会升级到重量级锁。

重量级锁:当多线程竞争很激烈,自旋等待失败后,升级位重量级锁。,mark word 如下 前30位指向monitor指针,最后两位表示成重量级锁 10 monitor解释:monitor相当于所对象与之对应的一个对象,每个对象创建的时候都会有一个与之对应的monitor,这个monitor重量级锁底层是mutex实现,是需要内核空间互斥量,涉及到用户态空间和内核态空间转换,非常消耗资源。 重量级执行流程:多个线程检查锁表示位10 ,直接去竞争monitor锁,如果成功,那么执行同步代码,失败当前线程加入到monitor的内部等待队列,等待当前执行完成,唤醒等待线程去竞争锁。 到这里已经把对象怎么加锁的变化过程梳理了一遍,有的地方没有到很细节,下面来个mark word汇总

问题答疑:

1、 所有new 出来的对象是否都存在堆区? 答案:否,因为如果jvm逃逸分析,一部分对象会存在栈区。

最新回复(0)