synchronized反编译后是由一对monitor-enter和monitor-exit指令实现的,反编译后可见!
通过反编译后代码可以看出:
对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。
对于同步代码块。JVM采用monitorenter、monitorexit两个指令来实现同步。
1,JDK1.6之前
这对指令的实现是依靠操作系统内部的互斥锁来实现的,期间会涉及到用户态到内存态的切换,所以这个操作是一个重量级的操作,性能较低。
2,JDK1.6之后
JVM对synchronized进行了优化,改了三个经历的过程,锁升级
JAVA的锁就是在对象的Markword中记录一个锁状态。
无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁
偏向锁: 只有1个线程争抢锁资源的时候.将线程拥有者标识为当前线程.
轻量级锁(自旋锁): 一个或多个线程通过CAS去争抢锁,如果抢不到则一直自旋.
重量级锁: 多个线程争抢锁,向内核申请锁资源,将未争抢成功的锁放到队列中直接阻塞.(当自旋的线程数量循环超过10次,或者线程等待的数量超过cpu的1/2,升级为重量级锁)
锁的具体升级过程(通常情况下)
当只有一个线程去争抢锁的时候,会先使用偏向锁,就是给一个标识,说明现在这个锁被线程a占有.
后来又来了线程b,线程c,说凭什么你占有锁,需要公平的竞争,于是将标识去掉,也就是撤销偏向锁,升级为轻量级锁,三个线程通过CAS进行锁的争抢(其实这个抢锁过程还是偏向于原来的持有偏向锁的线程).
现在线程a占有了锁,线程b,线程c一直在循环尝试获取锁,后来又来了十个线程,一直在自旋,(当自旋的线程循环超过10次,或者线程等待的数量超过cpu的1/2,升级为重量级锁)那这样等着也是干耗费CPU资源,所以就将锁升级为重量级锁,向内核申请资源,直接将等待的线程进行阻塞.
为什么要有锁的升级过程?
在最开始的时候,其实就是无锁直接到重量级锁,但是重量级锁需要向内核申请额外的锁资源,这就涉及到用户态和内核态的转换,比较浪费资源,而且大多数情况下,其实还是一个线程去争抢锁,完全不需要重量级锁.
什么情况下偏向锁才会升级为轻量级锁,什么时候轻量级锁才会升级为重量级锁?
只有一个线程的时候就是偏向锁(当偏向锁开启的时候,偏向锁默认开启),当争抢的线程超过一个,升级为轻量级锁.
当自旋的线程循环超过10次,或者线程等待的数量超过cpu的1/2,升级为重量级锁.
其实轻量级锁就适用于那种执行任务很短的线程,可能通过一两次自旋,就能够获取到锁.
开启偏向锁一定比轻量级锁高效吗? 不一定
比如在一开始已经知道某个资源就需要被多个线程争抢,此时就不需要开启偏向锁,因为偏向锁给了标识之后,还需要取消这个标识,重新抢锁, 比如在JVM中,偏向锁默认是延迟4秒才开始的,因为JVM在启动的时候需要多个线程竞争资源,并且这个都是一开始知道的.
并不是一定会有一个偏向锁->轻量级锁->重量级锁的过程,比如如果出现严重的耗时操作(sleep,或者wait等),就会直接由偏向锁升级为重量级锁.
JAVA的锁机制就是根据资源竞争的激烈程度不断进行锁升级的过程,锁升级是为了降低性能的消耗。
synchronized修饰成员方法时,默认的锁对象,就是当前对象
synchronized修饰静态方法时,默认的锁对象,当前类的class对象,比如User.class
synchronized修饰代码块时,可以自己来设置锁对象,比如
synchronized(this){
//线程进入,就自动获取到锁
//线程执行结束,自动释放锁
}