JVM关键字之Synchronized简单总结

作用

synchronized关键字,用来保障原子性,可见性,有序性。
为什么使用synchronized? 在多线程访问共享数据时,控制在同一时刻只能由一个线程执行方法或者代码块,即线程互斥,保证线程安全

应用

应用于方法:

public synchronized  void  addUser(){.....}

实例方法,锁住的该类的实例对象,

public synchronized static void addUser(){......}

静态方法,锁住的是类对象

应用于代码块:

   synchronized(this){......}

同步代码块。锁住的是该类的实例对象

synchronized(A.class){......}

同步代码块,锁住的是类实例

synchronized(object){......}

同步代码块。锁住的是某类的实例对象

原理

采用互斥手段达到同步的目的,临界区,互斥量,信号量都是主要实现互斥的方式;

synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的

synchronized经过编译后,
会在同步块前后形成monitorenter以及minotorexit两个字节码指令,这两个字节码都需要一个reference类型参数来指明要锁定和解锁的对象;在执行monitorenter指令时,即尝试获取锁,获取成功计数器加1,执行monitorexit指令时,即释放锁,锁计数器减1;
会在方法中加入ACC_SYNCHRONIZED标识,JVM通过标识判断是否为同步方法,从而实现同步调用

优化

首先说一下synchronized的不足,其由互斥手段实现同步,而互斥主要的问题是进行线程阻塞与唤醒所带来的性能问题,也称阻塞同步。

理论情况下,由synchronize修饰的代码中共享数据是否存在竞争,都会进行加锁,用户态核心态转换,维护锁计数器,检查是否有阻塞的线程是否需要被唤醒等操作。

在JDK1.4.2中引入自旋锁,在JDK1.6中引入自适应自旋锁

问题 : 线程阻塞唤醒都要在内核态进行,进而带来性能损耗

优化措施:让线程自旋获取锁n次,不放弃处理器;

自旋不能代替阻塞;自旋的优点是避免了线程切换的开销,缺点是占用CPU的执行时间,因此当执行 时间短,锁自旋效果更好,否则白白消耗处理器资源。

自旋默认值是10次,用户可使用参数-XX:PreBlockSpin来更改,而自适应自旋则时间不固定,由前一次在同一个锁上的自旋时间和锁的拥有者状态;如果一定时间内自旋成功获取到锁,且持有锁的线程正在运行中,则JVM可能会允许自旋更长时间,否则可能会直接跳过自旋

在JDK1.5中引入锁消除

问题:虚拟机即时编译器在运行时对一些代码要求同步,然而这段代码中不存在需要竞争的共享数据。

优化措施:判定栈中的数据不会被其他线程访问,则在即时编译时进行锁消除

深入JVM一书中提到,在JDK1.5之前,javac编译器会对string连接做自动优化,如下段代码,会使用线程安全类StringBuffer做append操作,在JDK1.5之后即时编译器判断不存在竞争共享数据后,即用StringBuilder做append操作。

public string concatString(string s1,strinng s2,string s3)
{
  return s1+s2+s3;
  //经过javac编译后会变成如下代码
  //StringBuffer str = new StringBuffer();
  //str,append(s1);
  //str,append(s2);
  //str,append(s3);
  //return str.tostring();
}
锁粗化

问题:连续的操作中对一个对象频繁加锁解锁;

优化措施:扩展加锁同步的范围

锁升级

在1.6前synchronized属于重量级锁,之后引入了偏向锁与轻量级锁。

实现轻量级锁和偏量锁的关键在于JAVA对象头中的第一部分,用于存储对象自身的运行时数据,称之mark word

简单了解下对象不同状态下的mark word
状态----------------------------标志位----------------------------存储内容
未锁定 ----------------------------01 ----------------------------对象哈希码,对象分代年龄
轻量级锁定 ----------------------------00----------------------------指向锁记录的指针
重量级锁定----------------------------10----------------------------指向重量级锁的指针
GC标记----------------------------11----------------------------空,不需要记录信息
可偏向----------------------------01----------------------------偏向线程ID,偏向时间戳,对象分代年龄

偏向锁

问题:在无竞争中,同步块会产生不必要的加锁解锁等操作

优化措施:无竞争中,消除同步。

当锁第一次被获取时,会设置对象头标志位为01,且CAS操作读取持有锁的线程ID并记录在对象的MARK WORD中,CAS成功后,持有锁的线程每次进入锁相关的同步块中,JVM都不再进行任何同步操作。

当有另一个线程尝试获取锁时,偏向模式取消,升级到轻量级锁。对象的MARK WORD存储内容由偏向内容变为轻量级内容

轻量级锁

问题:没有多线程竞争,重量级锁使用操作系统互斥量会产生性能消耗。

优化措施:在没有多线程竞争前提下,减少重量级锁使用操作系统互斥量产生的性能消耗。

进入代码块中,如果对象没有被锁定,虚拟机会在当前线程的栈帧中建立一个锁记录空间,拷贝一份对象的MARK WORD,JVM使用CAS操作将对象的MARK WORD更新为指向锁记录的指针。

如果更新失败了,JVM会检查对象的MARK WORD是否已经指向当前线程的锁记录,如果已经持有这个锁,则直接执行同步块,否则,自旋一段时间还未获取到锁或者有第3个线程过来时,轻量级锁升级为重量级锁。

在释放锁时,CAS替换对象的MARK WORD和栈帧中的锁记录,失败了,说明锁被其他线程访问了,升级了,则释放锁的同时,还需要唤醒被挂起的线程

在没有竞争时,使用CAS避免了互斥量开销
在有竞争时,不仅有互斥量的开销,还额外产生了CAS操作,会比传统重量级锁更慢

重量级锁

关键在于对象内部的一个监控器锁,其本质又依赖于底层的操作系统的Mutex Lock来实现,操作系统实现线程之间的切换需要从用户态转化为核心态。

对象监视器中主要存在3种组件:
wait_set 存储调用wait自行阻塞的线程;
entry_list 存储请求锁的线程
owner 当前获取到锁的线程、

wait_set和entry_list中的线程都处于阻塞状态,阻塞是由操作系统完成.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值