设为首页 加入收藏

TOP

Java性能之synchronized锁的优化(一)
2019-09-23 11:15:43 】 浏览:48
Tags:Java 性能 synchronized 优化

synchronized / Lock

1.JDK 1.5之前,Java通过synchronized关键字来实现锁功能

  • synchronized是JVM实现的内置锁,锁的获取和释放都是由JVM隐式实现的

2.JDK 1.5,并发包中新增了Lock接口来实现锁功能

  • 提供了与synchronized类似的同步功能,但需要显式获取和释放锁

3. Lock同步锁是基于Java实现的,而synchronized是基于底层操作系统的Mutex Lock实现的

  • 每次获取和释放锁都会带来用户态和内核态的切换,从而增加系统的性能开销
  • 在锁竞争激烈的情况下,synchronized同步锁的性能很糟糕
  • 在JDK 1.5,在单线程重复申请锁的情况下,synchronized锁性能要比Lock的性能差很多

4.JDK 1.6,Java对synchronized同步锁做了充分的优化,甚至在某些场景下,它的性能已经超越了Lock同步锁

实现原理

public class SyncTest {
public synchronized void method1() {
}
public void method2() {
Object o = new Object();
synchronized (o) {
}
}
}
$ javac -encoding UTF-8 SyncTest.java
$ javap -v SyncTest

修饰方法

public synchronized void method1();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=1, args_size=1
0: return
  1. JVM使用ACC_SYNCHRONIZED访问标识来区分一个方法是否为同步方法
  2. 在方法调用时,会检查方法是否被设置了ACC_SYNCHRONIZED访问标识
  • 如果是,执行线程会将先尝试持有Monitor对象,再执行方法,方法执行完成后,最后释放Monitor对象

修饰代码块

public void method2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: aload_2
13: monitorexit
14: goto 22
17: astore_3
18: aload_2
19: monitorexit
20: aload_3
21: athrow
22: return
  1. synchronized修饰同步代码块时,由monitorenter和monitorexit指令来实现同步
  2. 进入monitorenter指令后,线程将持有该Monitor对象,进入monitorexit指令,线程将释放该Monitor对象

管程模型

1.JVM中的同步是基于进入和退出管程(Monitor)对象实现的

2.每个Java对象实例都会有一个Monitor,Monitor可以和Java对象实例一起被创建和销毁

3.Monitor是由ObjectMonitor实现的,对应ObjectMonitor.hpp

4.当多个线程同时访问一段同步代码时,会先被放在EntryList中

5.当线程获取到Java对象的Monitor时(Monitor是依靠底层操作系统的Mutex Lock来实现互斥的)

  • 线程申请Mutex成功,则持有该Mutex,其它线程将无法获取到该Mutex

6.进入WaitSet

  • 竞争锁失败的线程会进入WaitSet
  • 竞争锁成功的线程如果调用wait方法,就会释放当前持有的Mutex,并且该线程会进入WaitSet
  • 进入WaitSet的进程会等待下一次唤醒,然后进入EntryList重新排队

7.如果当前线程顺利执行完方法,也会释放Mutex

8.Monitor依赖于底层操作系统的实现,存在用户态和内核态之间的切换,所以增加了性能开销

Java性能之synchronized锁的优化

 

ObjectMonitor() {
_header = NULL;
_count = 0; // 记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL; // 持有该Monitor的线程
_WaitSet = NULL; // 处于wait状态的线程,会被加入 _WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 多个线程访问同步块或同步方法,会首先被加入 _EntryList
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}

锁升级优化

  1. 为了提升性能,在JDK 1.6引入偏向锁、轻量级锁、重量级锁,用来减少锁竞争带来的上下文切换
  2. 借助JDK 1.6新增的Java对象头,实现了锁升级功能

Java对象头

  1. 在JDK 1.6的JVM中,对象实例在堆内存中被分为三部分:对象头、实例数据、对齐填充
  2. 对象头的组成部分:Mark Word、指向类的指针、数组长度(可选,数组类型时才有)
  3. Mark Word记录了对象和锁有关的信息,在64位的JVM中,Mark Word为64 bit
  4. 锁升级功能主要依赖于Mark Word中锁标志位和是否偏向锁标志位
  5. synchronized同步锁的升级优化路径:偏向锁 -> 轻量级锁 -> 重量级锁
Java性能之synchronized锁的优化

 

偏向锁

  1. 偏向锁主要用来优化同一线程多次申请同一个锁的竞争,在某些情况下,大部分时间都是同一个线程竞争锁资源
  2. 偏向锁的作用
  • 当一个线程再次访问同一个同步代码时,该线程只需对该对象头的Mark Word中去判断是否有偏向锁指向它
  • 无需再进入Monitor去竞争对象(避免用户态和内核态的切换)
  1. 当对象被当做同步锁,并有一个线程抢到锁时
  • 锁标志位还是01,是否偏向锁标志位设置为1,并且记录抢到锁的线程ID,进入偏向锁状态
  1. 偏向锁不会主动释放锁
  • 当线程1再次获取锁时,会比较当前线程的ID与锁对象头部的线程ID是否一致,如果一致,无需CAS来抢占锁
  • 如果不一致,需要查看锁对象头部记录的线程是否存活
  • 如果没有存活,那么锁对象被重置为无锁状态(也是一种撤销),然后重新偏向线程2
  • 如果存活,查找线程1的栈帧信息
  • 如果线程1还是需要继续持有该锁对象,那么暂停线程1(STW),撤销偏向锁,升级为轻量级锁
  • 如果线程1不再使用该锁对象,那么将该锁对象设为无锁状态(也是一种撤销),然后重新偏向线程2
  1. 一旦出现其他线程竞争锁资源时,偏向锁就会被撤销
  • 偏向锁的撤销可能需要等待全局安全点,暂停持有该锁的线程,同时检查该线程是否还在执行该方法
  • 如果还没有执行完,说明此刻有多个线程竞争,升级为
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇透彻讲解:并发编程的优缺点 下一篇PlayJava Day022

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目