Synchronized的使用方式
1.当synchronized修饰非静态方法时,锁住的是当前调用该方法的实例对象
2.当synchronized修饰的是非静态代码块,则锁住的是括号里面的实例对象
3.当synchronized修饰的是静态方法或者静态代码块时,则锁住的是类对象,因为不管一个class被
实例化多少次,静态方法和静态代码块在jvm中只会存在一份,因此当此类的所有实例对象在调用静态方法
或者静态代码块时共用同一把锁,因此也称为类锁
Synchronized的底层原理
synchronized是基于JVM内置锁实现的,通过内部对象Monitor(监视器锁)实现,基于进入和退出Monitor对
象来实现方法和代码块的同步。这是一个重量级锁,性能偏低。
Java1.5版本之后做了很大的变化,比如锁粗化,锁消除,偏向锁,轻量级锁,适应性自旋锁等技术来减少操作
的开销。synchronized性能已经有很大的提升了,已基本和Lock持平。
每个对象都有一个自己的Monitor监视器锁
Java对象在内存中的存储方案
HotSpot虚拟机中,对象在内存中被划分成三块区域存储:
1.对象头:比如hash码,对象所属的年代,对象锁,锁标识,偏向锁(线程)ID等。
2.实例数据:即创建对象时,对象中的成员变量,方法等。
3.对象填充:对象的大小必须是8字节的整数倍
以32位计算机为例,对象头中Mark Word的变化过程如下所示:
锁的膨胀升级过程
锁的状态总共有四种,无锁,偏向锁,轻量级锁,重量级锁。随着多个线程竞争共享资源,锁可以从偏向锁升级到
轻量级锁,再升级到重量级锁,而且这个过程是单向的,不可逆的。也就说锁只能从低到高升级,而不会出现锁降级。
-
偏向锁
偏向锁时jdk1.6之后加入的新锁,它是一种针对加锁操作的优化手段。经研究发现,大多数情况下,锁不仅不存在
多线程竞争,而写总是由同一线程多次获得锁,因此为了减少同一线程获取锁时进行的一些CAS操作而消耗一定的性能而
引入偏向锁。
偏向锁的核心思想是,如果一个线程获取了锁,那么锁就进入偏向该线程的模式,此时对象的Mark Word结构也
会变为偏向锁结构,当这个线程再次申请该锁时,无需做任何操作,即可获得该锁的使用权,这样就省去了大量的有关
申请锁的操作,从而提高程序的性能。
偏向锁的适用场景是对于锁的竞争不是很激烈的场合。
偏向锁失败后,不会立即升级为重量级锁,而是变成轻量级锁。
-
轻量级锁
从偏向锁升级成轻量级锁后,对象的Mark Word也会发生变化.
轻量级锁的依据是:对加锁的整个周期内的绝大部分时间不存在竞争,另一个线程自旋等待锁资源释放,不会交出
CPU使用权。
轻量级锁的适用场景是多个线程交替执行同步块的情况,如果同一时间有多个线程竞争锁资源的话,就会升级成
重量级锁.
-
自旋锁
自旋锁的实现是进行一定次数的空循环,一般是50次或者100个循环,在经过若干次循环后,如果得到锁,进进入
临界区,如果还不能获取到锁,就会将线程在操作系统层面挂起。
-
锁消除
Java虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除
没有必要的锁,可以节省毫无意义的请求锁时间,如下StringBuffer的append是一个同步方法,但是在add方法中的
StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,
JVM会自动将其锁消除