synchronized的作用范围:
-
synchronized修饰成员变量和非静态方法,锁的是对象的实例。
synchronized void method() { //业务代码 }
-
synchronized修饰静态方法时,锁住的是Class实例,因为static是JVM层面的方法,属于Class而不属于对象。
synchronized void staic method() { //业务代码 }
-
synchronized修饰一个代码块时,锁住的是所有代码块中的对象。
synchronized(this) { //业务代码 }
synchronized实现原理:
- synchronized同步代码块:synchronized关键字经过编译之后,会在同步代码块前后分别形成monitorenter和monitorexit字节码指令,在执行monitorenter指令的时候,首先尝试获取对象的锁,如果这个锁没有被锁定或者当前线程已经拥有了那个对象的锁,锁的计数器就加1,在执行monitorexit指令时会将锁的计数器减1,当减为0的时候就释放锁。如果获取对象锁一直失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
- 同步方法:方法级的同步是隐式的,无须通过字节码指令来控制,JVM可以从方法常量池的方法表结构中ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。当方法调用的时,调用指令会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要求先持有monitor对象,然后才能执行方法,最后当方法执行完(无论是正常完成还是非正常完成)时释放monitor对象。在方法执行期间,执行线程持有了monitor对象,其他线程都无法再次获取同一个monitor对象。
对象头的存储结构以及Mark Word的状态变化。
锁状态 | 存储内容 | 标志位 |
---|---|---|
无锁 | 对象的hashCode、对象分代年龄、是否是偏向锁(0) | 01 |
偏向锁 | 偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1) | 01 |
轻量级锁 | 指向栈中锁记录的指针 | 00 |
重量级锁 | 指向互斥量的指针 | 11 |
从对象头的存储内容可以看出锁的状态都保存在对象头中。标志位为01的时候锁为无状态,当其从轻量级锁膨胀为重量级锁时,标志位从01变为10。其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。
总的来说:关于Synchronized的实现在java对象头里较为简单,只是改变一下标识位,并将指针指向monitor对象的起始地址。
Monitor对象
在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)。ObjectMonitor中有几个关键属性:
- _count用来记录该线程获取锁的次数
- WaitSet存放处于wait状态的线程队列
- _EntryList存放处于等待获取锁block状态的线程队列,即被阻塞的线程
- _owner指向持有ObjectMonitor对象的线程
图引用自:深入理解Java并发之synchronized实现原理_zejian的博客-CSDN博客_synchronized原理
当多个线程同时访问一段同步代码时,首先会进入_EntryList队列中,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器 _count加1,若线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,_count自减1,同时该线程进入_WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。
synchronized优化(锁升级过程)
jdk6之前synchronized依赖于底层的操作系统的Mutex Lock来实现的,操作系统实现线程之间切换比较耗时间。JDK6之后为了减少获得锁和释放锁所带来的性能消耗,引入了偏向锁、轻量级锁、自旋锁
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步块场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果始终得不到锁竞争的线程,使用自旋会消耗CPU | 追求响应速度,同步块执行速度非常快 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较慢 |
锁升级流程:
参考文章:
2.深入理解Java并发之synchronized实现原理_zejian的博客-CSDN博客_synchronized原理