java并发的synchronized关键字

Java并发编程存在线程安全问题的因素主要有两点:一是存在共享数据,二是存在多条线程共同操作共享数据。在 Java 中,为了解决线程安全问题,引入了关键字 synchronized互斥锁。它可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作,保证原子性),同时synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能)。

synchronized三个用法

1,修饰实例方法,作用于当前实例加锁;

2,修饰静态方法,作用于当前类对象加锁;

3,修饰代码块,指定加锁对象,对给定对象加锁。

底层原理

在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。对象头主要结构是由Mark Word 和 Class Metadata Address 组成。Mark Word存储着对象的HashCode、分代年龄、锁标记位等。锁的状态总共有五种,无锁状态、偏向锁、轻量级锁,自旋锁和重量级锁。锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

1,偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。但是对于锁竞争比较激烈的场合,偏向锁就失效了。

2,轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

3,自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),如果得到锁,就顺利进入临界区,如果还不能获得锁,那就会将线程在操作系统层面挂起,只能升级为重量级锁了。

4,重量级锁,就是通常说synchronized的对象锁,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因。在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因。


synchronized代码块与synchronized方法

1,synchronized语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor ,重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。

2,synchronized方法的实现是是隐式的,JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。如果是同步方法,执行线程将先持有monitor, 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。

3,synchronized是可重入性的,在获取当前实例对象锁后进入synchronized代码块执行同步代码,并在代码块中调用了当前实例对象的另外一个synchronized方法,再次请求当前实例锁时,将被允许,进而执行方法体代码,这就是重入锁最直接的体现,需要特别注意另外一种情况,当子类继承父类时,子类也是可以通过可重入锁调用父类的同步方法。注意由于synchronized是基于monitor实现的,因此每次重入,monitor中的计数器仍会加1。

你可能感兴趣的:(java并发的synchronized关键字)