参考资料:https://www.cnblogs.com/myseries/p/12213997.html

synchronized三种用法

  1. 修饰实例方法,对当前实例对象this加锁
  2. 修饰静态方法,对当前类的Class对象加锁
  3. 修饰代码块,指定加锁对象,对给定对象加锁

代码实例:

  1. public class SynchronizedDemo {
  2. //修饰实例方法:锁的是当前实例对象
  3. public synchronized void test() {
  4. }
  5. //修饰静态方法,锁的是当前类的Class对象
  6. public static synchronized void test2() {
  7. }
  8. public void test3() {
  9. //修饰代码块,锁定指定对象
  10. synchronized (this) {
  11. }
  12. //修饰代码块,锁定指定对象
  13. synchronized (SynchronizedDemo.class) {
  14. }
  15. }
  16. }

synchronized实现原理

Java对象的组成:

  • 对象头
    • 标记字段:默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化
    • 类型指针。对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 实例数据:存放类的数据信息,父类的信息
  • 填充字节:由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。

image.png
synchronized修饰代码块和方法的实现原理:

  • synchronized修饰代码块的实现:进入同步代码块,先执行monitorenter指令,退出同步代码块,执行monitorexit指令,有两个monitorexit指令,一个是正常退出执行的,另一个是异常退出时执行的。
  • synchronized修饰同步方法的实现:执行方法前,会先判断否有标志位ACC_SYNCHRONIZED,该标记位会隐式去调用monitorenter和monitorexit这两个指令。

synchronized锁升级

在JDK1.6之前synchronized锁是重量级锁,性能很差,因为为操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

JSK1.6之后引入了偏向锁和轻量级锁,对从JVM层面对synchronized较大优化
image.png

tips:锁升级过程不可逆。

偏向锁:
大部分情况下只有一个线程进行同步调用,此时就没必要进行加锁,这个锁会偏向第一个获得它的线程,该线程会利用CAS操作,将线程ID插入对象头标记字段中,同时修改偏向锁的标记位。然后再加下来的操作中,会根据调用的线程ID进行对比,如果是第一个线程,则不需要进行加锁,直接执行方法即可,不再需要去进行加锁或者解锁操作,如果线程ID不一致,此时就存在了竞争。此时可能就要根据另外线程的情况,可能是重新偏向,也有可能是做偏向撤销,但大部分情况下就是升级成轻量级锁了。

轻量级锁:
偏向锁失效之后就需要进行锁撤销,锁撤销之后就升级为轻量级锁。
轻量级锁主要有两种:

  • 自旋锁。就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。但是这个过程会消耗CPU,所以适合同步代码块执行的很快的场景

为了解决空循环消耗cpu的问题,必须要给线程空循环设置一个次数,当线程超过了这个次数,我们就认为,继续使用自旋锁就不适合了,此时锁会再次膨胀,升级为重量级锁

  • 自适应自旋锁。线程空循环等待的自旋次数并非是固定的,而是会动态着根据实际情况来改变自旋等待的次数。

几种锁的优缺点:
image.png

错误的加锁方式:

  • synchronized(newObject()) 每次调用的创建的都是不同的锁, 相当于无锁
  • String,Boolean在实现了都用了享元模式,即值在一定范围内,对象是同一个。所以看似是用了不同的对象,其实用的是同一个对象。会导致一个锁被多个地方使用。

    1. private Integer count;
    2. public synchronized void test4() {
    3. synchronized (count) {
    4. }
    5. }

    正确的加锁方式:

    1. //普通对象锁
    2. private final Object lock = new Object();
    3. //静态对象锁
    4. private static final Object staticLock = new Object();
    5. public synchronized void test5() {
    6. synchronized (lock) {
    7. }
    8. synchronized (staticLock) {
    9. }
    10. }