01synchronized作用
synchronized提供一中互斥机制,也就是同一时刻,只能有一个线程访问同步资源。
02
jvm指令分析
先来看一个类,看一下他的JVM指令,使用javap反汇编,看一小段jvm指令
能够看出synchronized是基于monitor enter 和monitor exit 实现互斥的,因此我们介绍一下他们是如何实现的。
1.Monitorenter
每一个对象与一个monitor,一个monitor的lock的锁只能被一个线程同一时刻获得,当一个线程获得与对象monitor的所有权是会发生的几件事情
1.monitor的计数器为0时,说明没有线程获得mmonitor 的lock,某个线程在获得lock计数器就会加.
2.该线程已经获得monitor在次重入获得,技术器再次加 一.
3.当另外一个线程尝试获得monitor的lock,将会被挂起,知道monitor的计数 器为0.才能再次尝试获得monitor的所有权.
2.monitorexit
想要释放锁,前提是该线程获得monitor的lock,就是要把monitor的计数器减一,就会释放锁,其他挂起的线程将会尝试获得monitor的所有权.
注意
1.monitor关联的对象不能为空,monitor将无从谈起.
2.synchronized 作用太大,代表性能越差.
3.不停的monitor企图锁相同的对象起不到互斥的作用.
4.多个锁交叉会导致死锁 .
接着我们在介绍一下两个特别的monitor ,this monitor 和class monitor,还是一样先看一段代码。
menthod1,method2,method3是一样都是this monitor ,metnod4,method5,method6也是一样的是class monitor,我们再反汇编一下,看看其中一段指令。
我们看看method2和method3有什么不一样,我们看到标红的位置,method2没有monitor enter和monitor exit,而发现有一个关键此ACC_SYNCHRONIZED,其实这是因为JVM使用ACC_SYNCHRONIZED访问一个方法是够是同步方法,当方法调用时,调用指令将会检查方法是哦股被设置成ACC_SYNCHRONIZED访问标志,如果有该标志,执行线程将现持有Monitor对象,然后执行方法,在该方法运行期间,其他线程无法获得Monitor对象,执行完方法后,在释放Monitor对象。
03
synchronized优化
由于Monitor是依赖于底层的操作系统实现,所以有用户态和内核态的切换,性能会差一些,因此就引入了偏向锁,轻量级锁,重量级锁进行优化;
1.偏向锁.
由于很多情况下,不仅不存在线程净增,而且总是由同一线程多次获得,因为每次操作都会发生用户态和内核态的切换
为了减少获取锁的代价,引入了偏向锁;
2.轻量级锁.
但是当多有一定的线程竞争就会升级为轻量级锁,轻量级 锁用于线程交替执行的场景,由于大多数线程持有锁是很短的时间,所以轻量级锁就会自旋重新获得锁,不然线程阻塞也是一种性能损失(如果锁竞争激烈也会占用cpu资源,反而会影响开销);
3.重量级锁.
在高并发场景下最终会是会升级到重量级锁;
他们升级的流程大体是:
1.检查MarkWord是否存在线程的id;
2.如果没有存储线程id,获得偏向锁,将MarkWord中的线程ID设置成自己;
3.如果有存储线程id,线程使用CAS操作把这个锁的线程ID记录在对象Mark Word之中,同时置是否偏向标志位1.(以后该线程在进入和退出同步快是不需要进行CAS操作来加锁和解锁,只需要测试一下对象头的Mark word里面是否存储着只想当前线程的偏向锁,如果成功表示线程已经获得了锁);
4.如果CAS操作失败,则当达到全局安全点时,获得偏向锁的线程被挂起,膨胀为轻量级锁,同时撤销偏向锁且设置是否偏向标志为0;
5.当有线程竞争这个锁,进行CAS操作,如果成功获得轻量级锁,执行同步代码
6.如果失败,通过自旋不断尝试获取锁,从而避免线程挂起阻塞.
7.如果自旋依然失败,就会升级到重量级锁,标志改成10,反之成功,继续轻量级锁;
当然还要其他的优化方式:
1.逃逸分析技术,判断同步快使用多的对象是否只能被一个线 程访问,而没 有发布到其他线程,如果是的化,就不会JIT就不会生成申请锁和释放锁的的 机器码,消除锁的使用;
2.锁的粗化,JIT把相邻的同步快使用同一个锁;
3.较少锁粒度,把锁对象分成一个数组或一个队列,减少净增 提高并行度,正 如JDK1.7实现的ConcurrrentHashMap;