java多线程——synchronize关键字详解

synchronize的基本使用

在并发编程中synchronized一直承担的非常重要的作用,它有以下三种使用方式

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

当一个线程访问一个同步代码块的时候,首先获得锁,只有当退出或抛出异常的时候,才会释放锁。

那么它究竟是如何实现的呢?我们通过反编译如下代码

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

可以得到如下结果
java多线程——synchronize关键字详解_第1张图片
我们可以看到在方法执行的开始和结尾,虚拟机为我们的程序加上monitorentermonitorexit,结果也就一目了然JVM基于进入和退出对象监视器Monirot的方法来实现方法同步和代码同步。每个对象只有一个对象监视器,当程序执行到monitor.enter指令的时候,首先会去尝试获取对象监视器,如果获取成功,则进入同步方法,如果获取失败则会进入同步队列进行等待,直到其他线程执行到monitor.exit释放对象监视器。

对于同步块的实现使用了monitor.entermonitor.exit指令,同步方法的实现则是依靠方法修饰符上ACC_SYNCHRONIZED来完成的。其本质就是对对象监视器的获取,因为获取对象监视器的过程是一个排他的过程,也就是说同一时刻只有一个线程可以获得对象监视器,过程如下图。
java多线程——synchronize关键字详解_第2张图片

jdk1.6之后的锁升级

一直以为java的synchronize是重量级锁,但是没想到在jdk1.6之后,对synchronize的锁进行了优化引入了“偏向锁”和“轻量级锁”,在1.6中的锁一共有4种状态,从低到高分别是:无所状态、偏向锁状态、轻量级锁和重量级锁,这几种锁会随着线程的进行而进行升级,但是不能降级。这些操作都是为了在线程之间更高效的实现共享数据 ,以及竞争带来的问题。

偏向锁

当线程访问同步块时,首先会使用 CAS将线程 ID 更新到锁对象的 Mark Word中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。

释放锁

当有另外一个线程获取这个锁时,这个时候CAS将会更新失败,这个时候偏向锁的线程将会释放锁,释放时会等待全局安全点(在这一个时间点没有字节码运行),接着会暂停拥有偏向锁的线程,检查偏向锁线程是否处于活动状态,如果不处于活动状态,则将对象头设置为无锁状态;如果线程仍然活着,则根据锁对象目前是否被锁来判定将对象头中的 Mark Word 设置为无锁或者是轻量锁状态,最后唤醒暂停线程。

偏向锁可以提高带有同步却没有竞争的程序性能,但如果程序中大多数锁都存在竞争时,那偏向锁就起不到太大作用。可以使用-XX:-userBiasedLocking=false来关闭偏向锁,并默认进入轻量锁。

轻量级锁

线程进入同步代码块之前,线程会在栈帧中记录锁信息的区域Lock Record,同时将锁的对象头Mark Word拷贝到Lock Record,使用CASMark Word更新为指向锁记录的指针:

如果更新成功,当前线程获取锁。
如果失败,表示其他线程竞争锁,当前线程尝试使用自旋来获取锁。

轻量级锁解锁

轻量级锁解锁时,首先使用CAS操作,尝试将锁记录替换回锁对象的对象头,如果成功,则表示没有发生竞争。如果失败,表示当前锁存在竞争,锁会膨胀为重量级锁

你可能感兴趣的:(多线程)