Synchronized 关键字原理

        Synchronized关键字解决的是多个线程之间访问资源的同步性, synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

使用Java的小伙伴都知道synchronized关键字是解决并发问题常用解决方案,常用的有以下三种使用方式:

       修饰代码块,即同步语句块,其作用的范围是大括号{}括起来的代码, 作用的对象是调用这个代码块的对象。

       修饰普通方法,即同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。

       修饰静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。

关于synchronized的使用方式以及三种锁的区别在学习指南中讲解的十分清楚。

一 Synchronized 原理

      实现原理:JVM 是通过进入、退出 对象监视器(Monitor) 来实现对方

法、同步块的同步的,而对象监视器的本质依赖于底层操作系统的   互斥锁

(Mutex Lock) 实现。

      具体实现是在编译之后在同步方法调用前加入一个monitor.enter 指令,在退出方法和异常处插入monitor.exit 的指令。

对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程

monitor.exit 之后才能尝试继续获取锁。

流程图如下:

Synchronized 关键字原理_第1张图片

通过一段代码来演示:

public static void main(String[] args) {
 synchronized (Synchronize.class){
    System.out.println("Synchronize");
   }
}

使用javap -c Synchronize 可以查看编译之后的具体信息。

Synchronized 关键字原理_第2张图片

       可以看到在同步块的入口和出口分别有monitorenter 和monitorexit 指令。当执行monitorenter 指令时,线程试图获取锁也就是获取monitor

(monitor对象存在于每个Java对象的对象头中,synchronized锁便是通 过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因)的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加 

    1.相应的在执行monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

在synchronized修饰方法时是添加ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

Synchronized 关键字原理_第3张图片

synchronized的特点:

Synchronized 关键字原理_第4张图片

二.Synchronized 优化

      从synchronized的特点中可以看到它是一种重量级锁,会涉及到操作系统 状态的切换影响效率,所以JDK1.6中对synchronized进行了各种优化,为了能减少获取和释放锁带来的消耗引入了偏向锁和轻量锁。

偏向锁

      引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。

1偏向锁的获取过程

(1)访问Mark Word中偏向锁的标识是否设置成“1”,锁标志位是否为“01”——确认为可偏向状态。

(2)如果为可偏向状态,判断线程ID是否指向当前线程,如果是进入步骤(5),否则进入步骤(3)。

(3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。

(4)如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点

(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

(5)执行同步代码。

2偏向锁的释放

     偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点

(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

3轻量锁

    轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。

1轻量级锁的加锁过程

(1)在代码进入同步块时,如果同步对象锁状态为无锁状态(锁标志位 为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。

(2)拷贝对象头中的Mark   Word复制到锁记录中。

(3)拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock Record里的owner指针指向object mark word。如果更新成功,则执行步骤(3),否则执行步骤(4)。

(4)如果这个更新动作成功,那么这个线程就拥有了该对象的锁,并且 对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态。

(5)如果这个更新操作失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁, 那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状   态。而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞, 而采用循环去获取锁的过程。

Synchronized 关键字原理_第5张图片

你可能感兴趣的:(多线程,java,并发编程,jvm,面试)