synchronized原理

1.原理

synchronized是一种对象锁,是可重入的,但是不可中断的(这个不可中断指的是在阻塞队列中排队是不可中断),非公平的锁。
synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现。
加了synchronized后,在字节码会有二条指令,如代码第4和13标识位。

synchronized (Test3.class){
            System.out.println(1);
        }

```java
 	   4: monitorenter
       5: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: iconst_1
       9: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      12: aload_1
      13: monitorexit
      


  1. monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行 monitorenter指令时尝试获取monitor的所有权,过程如下:

a. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor 的所有者;
b. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
c. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝 试获取monitor的所有权;

2.monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减 1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去 获取这个 monitor 的所有权。

synchronized加锁加在对象上,对象是如何记录锁状态的呢?答案是锁状态是被记录在每个对象 的对象头(Mark Word)中,下面我们一起认识一下对象的内存布局

2.对象内存分布
对象存储布局

  1. 对象头:Java对象头一般占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit, 在64位虚 拟机中,1个机器码是8个字节,也就是64bit),但是 如果对象是数组类型,则需要3个 机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的 元数据来确认数组的大小,所以用一块来记录数组长度
  2. 实例数据:存放类的属性数据信息,包括父类的属性信息;
  3. 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
    在这里插入图片描述
    3.锁升级
    在JDK1.5之前一直是一种重量级锁的状态,也就是每次加锁和释放锁都直接和操作系统打交道。

JDK1.5后,就做了很多优化偏向锁,轻量级锁,自适应自旋等。

在这里插入图片描述
偏向锁
偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。

偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时, 无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。

所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就 失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的 是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。下面我们接着了解轻量级锁。

默认开启偏向锁
开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking

轻量级锁
倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞 争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场 合,就会导致轻量级锁膨胀为重量级锁。

自旋锁
轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要 从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线 程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或 100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是 自旋锁的优化方式,这种方式确实也是可以提升效率的。

最后没办法也就只能升级为重量级锁了。

锁的比较

⚠️注意啊,锁的升级是不可逆的!!!!!!!

synchronized能不能禁止指令重排序

首先一定要明确:指令重排序和有序性是不一样的。这一点非常重要。
我们经常都会这么说:

①、volatile能保证内存可见性、禁止指令重排序但是不能保证原子性。

②、synchronized能保证原子性、可见性和有序性。

注意:这里的有序性并不是代表能禁止指令重排序。

II)、有序性
那么既然synchronized不能禁止指令重排序,那么他保证的有序性是什么有序呢?

它的本质是让多个线程在调用synchronized修饰的方法时,由并行(并发)变成串行调用,谁获得锁谁执行。
所以synchronized保证顺序性是指的将并发执行变成了串行,但并不能保证内部指令重排序问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值