背景

  • 6之前是重量级锁依赖硬件实现。与Lock性能差距很大。
  • 6之后进行了优化,锁升级机制,与Lock性能相当。

    原理

  • 保证方法或代码块在运行时,同一时刻只有一个方法可进入临界区,同时还可保证共享变量的内存可见性。

  • java每个对象都可加锁,这是Synchronized实现同步的基础

    • 普通同步方法,锁是当前实例对象
    • 静态同步方法,锁是当前类的Class对象
    • 同步方法块,锁的括号里面的对象

      1. public calss SynchronizedTest{
      2. public Synchronized void test(){
      3. }
      4. public void test2(){
      5. Synchronized(this){
      6. }
      7. }
      8. }

6之前

同步代码块
  • 同步代码块是使用monitorenter和monitorexit指令实现的,同步(这里看不出要JVM底层实现)方法依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。
  • monitor enter指令插入同步代码块开始的地方,monitor exit插入结束位置,JVM需要保证这两个一一对应。
  • 对象都有一个monitor与之关联,一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将尝试获取monitor所有权,即尝试获取锁。

    同步方法
  • Synchronized被翻译成普通方法调用和返回指令如:invokevirtual、areturn指令,在JVM字节码层面没有任何特别的指令来实现Synchronized修饰的方法,而是Class文件的方法表中将该方法的access——flags字段中的Synchronized标志位置1,标识该方法时同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象标识Klass作为锁对象。

    JAVA对象头、monitor
  • java对象头和monitor时实现Synchronized的基础

  • hotspot虚拟机的对系统包括:Mark Word(标记字段)、Klass Pointer(类型指针)
  • Java对小偷的存储结构(32位虚拟机)
    特别篇--Synchronized - 图1
  • Mark Word随着程序的运行发生变化。
    特别篇--Synchronized - 图2

6之后

锁优化
  • 6对锁的实现引入了大量的锁优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
  • 锁主要存在四种状态:无锁、偏向锁、轻量级锁、重量级锁。他们随着竞争的激烈而逐渐升级。注意锁的升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

    锁升级过程(锁膨胀)

  • 无锁,无竞争

  • 偏向锁(cas比较信息)
  • 轻量级锁(cas尝试改变指针)
  • 锁自选
  • 重量级锁

    自旋锁
  • 背景:线程的阻塞和唤醒需cpu从用户态转为核心态,频繁会带来压力,同时很多应用的锁只持续很短的一段时间,很短,切换不值的,自此引入自旋锁。

  • 解释:自旋锁就是让线程等一会,不会立即挂起,看前面线程是否一会就释放锁。怎么等待?无意义的循环(即自旋)。
  • 等待时间必须有个限制,否则长时间获取不到,一直自旋浪费资源得不偿失。6默认开启,默认10次自旋。可通过参数 XX:PreBlockSpin来调整。

    适应性自旋锁
  • 6引入

  • 所谓自适应就是自旋的次数不再是固定的,由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
  • 线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。
  • 反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。

    锁消除
  • 有些情况下JVM检测到不可能存在共享数据竞争,这时JVM会对同步锁进行锁消除,依据是逃逸分析的数据支持。

  • 不存在数据竞争还加锁?我们不显式加锁,但有些API如StringBuffer、Vector、HashTable、COW等都存在隐形的锁操作。

    锁粗化
  • 概念:将多个连续加锁、解锁操作连接在一起,扩展成一个更大的锁。如循环内操作加锁,JVM检测到会将锁移到循环外。

    轻量级锁
  • 目的:没有多线程竞争的奇纳提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当关闭偏向锁功能或多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁。

  • 轻量级锁的获取和释放是通过CAS实现的

特别篇--Synchronized - 图3

偏向锁
  • 目的:为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。轻量级锁的加锁解锁操作是需要依赖多次CAS原子指令的,那么偏行锁是如何让来减少不必要的CAS操作呢?
  • 获取:检测Mark Word是否位偏行锁状态,是否为偏向锁1,锁标识位位01;
    • 若位可偏向锁,检测线程ID是否位当前线程ID,是就执行同步代码块;否通过CAS操作竞争所,成功替换ID;失败偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块。
  • 释放:采用了一种只有竞争才会释放锁的机制,线程是不会主动释放偏向锁,需等待其他线程来竞争。偏向锁的撤销需要等全局安全点(这个时间点是没有正在执行的代码)。步骤如下:
    • 暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。
    • 车i西安偏向锁,恢复到无锁状态(01)或轻量级锁状态。

特别篇--Synchronized - 图4

重量级锁
  • 重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统Mutex Lock实现,操作系统实现线程之间的切换需要用户态转为内核态,成本很高。

    特性保证

  • 有序性

  • 可见性
    • 内存强制刷新
  • 原子性
    • 单一线程持有
  • 可重入性

    • 计数器

      Synchronized与Lock区别

  • sync是关键字,jvm层面自动做,Lock是接口,JDK的api

  • sync自动释放,lock手动。
  • sync不可中断,lock均可。
  • sync不可知锁状态(是否拿到锁)lock可知。
  • sync可锁方法代码块,lock只能代码块。
  • sync非公平锁,Lock的子类reentrentLock可选择。
  • sync锁升级不可逆

    Lock

  • 读写锁

  • 可重入锁
    • 非公平
      • cas
      • tryAcquire
      • acquireQueued
    • 公平
      • 若是当先尺有所的线程,可重入。

参考


Synchorized(this)与Synchorized(class)区别

对象锁Synchorized(this)

在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。

类锁Synchorized(class)

在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。

修饰代码块

  • synchronized(this|object){}
  • synchronized(类.class){}

    修饰方法

  • 修饰非静态方法

  • 修饰静态方法

    根据获取的锁分类

  • synchronized(this|object){}

  • 修饰非静态方法

    获取类锁

  • synchronized(类.class){}

  • 修饰静态方法,非静态方法

    总结

    1、对于静态方法,由于此时对象还未生成,所以只能采用类锁;
    2、只要采用类锁,就会拦截所有线程,只能让一个线程访问。
    3、对于对象锁(this),如果是同一个实例,就会按顺序访问,但是如果是不同实例,就可以同时访问。
    4、如果对象锁跟访问的对象没有关系,那么就会都同时访问。