synchronized作为java语言中的并发关键词,其在代码中出现的频率适当高频,大多数开发者在涉及到并发场景时,一般都会下意识得选取synchronized。

synchronized在代码中主要有三类用法,根据其用法不同,所获取的锁目标也不同,如下所示:

  • 润饰代码块:这种用法一般叫做同步代码块,获取的锁目标是在synchronized中显式指定的
  • 润饰实例办法:这种用法一般叫做同步办法,获取的锁目标是当时的类目标
  • 润饰静态办法:这种用法一般叫做静态同步办法,获取的锁目标是当时类的类目标

下面咱们一起来测验下三种方式下,目标锁的归属及锁晋级进程,SynchronizedTestClass类代码如下:

import org.openjdk.jol.info.ClassLayout;
​
public class SynchronizedTestClass {
  private Object mLock = new Object();
  public void testSynchronizedBlock(){
    System.out.println("before get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(mLock).toPrintable());
    synchronized (mLock) {
      System.out.println("testSynchronizedBlock start:"+Thread.currentThread().getName());
      System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(mLock).toPrintable());
      try {
        Thread.sleep(10000);
       } catch (InterruptedException e) {
        throw new RuntimeException(e);
       }
      System.out.println("testSynchronizedBlock end:"+Thread.currentThread().getName());
     }
   }
​
  public synchronized void testSynchronizedMethod() {
    System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(this).toPrintable());
    System.out.println("testSynchronizedMethod start:"+Thread.currentThread().getName());
    try {
      Thread.sleep(10000);
     } catch (InterruptedException e) {
      throw new RuntimeException(e);
     }
    System.out.println("testSynchronizedMethod end:"+Thread.currentThread().getName());
   }
​
  public static synchronized void testSynchronizedStaticMethod() {
    System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(SynchronizedTestClass.class).toPrintable());
    System.out.println("testSynchronizedStaticMethod start:"+Thread.currentThread().getName());
    try {
      Thread.sleep(10000);
     } catch (InterruptedException e) {
      throw new RuntimeException(e);
     }
    System.out.println("testSynchronizedStaticMethod end:"+Thread.currentThread().getName());
   }
}

同步代码块

在main函数编写如下代码,调用SynchronizedTestClass类中包括同步代码块的测验办法,如下所示:

public static void main(String[] args) {
  SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();
  ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedBlock();
     }
   });
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedBlock();
     }
   });
}

运转结果如下:

synchronized实现原理

从上图能够看出在线程2获取锁前,mLock处于无锁状况,等线程2获取锁后,mLock目标晋级为轻量级锁,等线程1获取锁后晋级为重量级锁,有同学要问了,你在多线程与锁中不是说了synchronized锁晋级有四个吗?你是不是写BUG了,当然没有啊,现在咱们来看看倾向锁去哪儿了?

倾向锁

关于不同版本的JDK而言,其针对倾向锁的开关和装备均有所不同,咱们能够经过履行java -XX:+PrintFlagsFinal -version | grep BiasedLocking来获取倾向锁相关装备,履行命令输出如下:

synchronized实现原理

从上图能够看出在JDK 1.8上,倾向锁默认敞开,具有4秒延时,那么咱们修改main内容,延时5秒开端履行,看看现象怎么,代码如下:

public static void main(String[] args) {
  try {
    Thread.sleep(5000);
   } catch (InterruptedException e) {
    throw new RuntimeException(e);
   }
  SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();
​
  ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedBlock();
     }
   });
​
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedBlock();
     }
   });
}

输出如下:

synchronized实现原理

从上图能够看出在延迟5s履行后,mLock锁变成了无锁可倾向状况,结合上面两个示例,咱们能够看出,在轻量级锁和倾向锁阶段均有可能直接晋级成重量级锁,是否晋级依靠于当时的锁竞赛联系,据此咱们能够得到synchronized锁晋级的常见进程,如下图所示:

synchronized实现原理

能够看出,咱们遇到的两种情况别离对应晋级路线1和晋级路线4。

同步办法

运用线程池调用SynchronizedTestClass类中的同步办法,代码如下:

public static void main(String[] args) {
  SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();
​
  ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedMethod();
     }
   });
​
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedMethod();
     }
   });
}

运转结果如下:

synchronized实现原理

能够看出,在调用同步办法时,直接晋级为重量级锁,同一时间,有且仅有一个线程在同步办法中履行,其他函数在同步办法入口处堵塞等候。

静态同步办法

运用线程池调用SynchronizedTestClass类中的静态同步办法,代码如下

  public static void main(String[] args) {
    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
      @Override
      public void run() {
        SynchronizedTestClass.testSynchronizedStaticMethod();
       }
     });
    testSynchronizedBlock.execute(new Runnable() {
      @Override
      public void run() {
        SynchronizedTestClass.testSynchronizedStaticMethod();
       }
     });
   }

运转结果如下:

synchronized实现原理

能够看出,在调用静态同步办法时,直接晋级为重量级锁,同一时间,有且仅有一个线程在静态同步办法中履行,其他函数在同步办法入口处堵塞等候。

前面咱们看的是多个线程竞赛同一个锁目标,那么假定咱们有三个线程别离履行这三个函数,又会怎样呢?代码如下:

public static void main(String[] args) {
  SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();
​
  ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      SynchronizedTestClass.testSynchronizedStaticMethod();
     }
   });
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedMethod();
     }
   });
  testSynchronizedBlock.execute(new Runnable() {
    @Override
    public void run() {
      synchronizedTestClass.testSynchronizedBlock();
     }
   });
}

运转结果:

synchronized实现原理

能够看到,3个线程各自运转,互不影响,这也进一步印证了前文所说的锁目标以及MarkWord中符号锁状况的概念。

synchronized完成原理

上面已经学习了synchronized的常见用法,关联的锁目标以及锁晋级的进程,接下来咱们来看下synchronized完成原理,仍然以上面的SynchronizedTestClass为例,检查其生成的字节码来了解synchronized关键字的完成。

同步代码块

testSynchronizedBlock其所对应的字节码如下图所示:

synchronized实现原理

从上图代码和字节码对应联系能够看出,在同步代码块中获取锁时运用monitorenter指令,开释锁时运用monitorexit指令,且会有两个monitorexit,保证在当时线程异常时,锁正常开释,防止其他线程等候死锁。

所以synchronized的同步机制是依靠monitorenter和monitorexit指令完成的,而这两个指令操作的便是mLock目标的monitor锁,monitorenter尝试获取mLock的monitor锁,如果获取成功,则monitor中的计数器+1,同时记录相关线程信息,如果获取失利,则当时线程堵塞。

Monitor锁便是存储在MarkWord中的指向重量级锁的指针所指向的目标,每个目标在构造时都会创建一个Monitor锁,用于监督当时目标的锁状况以及持锁线程信息,

同步办法

testSynchronizedMethod其所对应的字节码如下图所示:

synchronized实现原理

能够看到同步办法依靠在函数声明时添加ACC_SYNCHRONIZED符号完成,在函数被ACC_SYNCHRONIZED润饰时,调用该函数会请求目标的Monitor锁,请求成功则进入函数,请求失利则堵塞当时线程。

静态同步办法

testSynchronizedStaticMethod其所对应的字节码如下图所示:

synchronized实现原理

和同步办法相同,同步静态办法也是在函数声明部分添加了ACC_SYNCHRONIZED符号,也同步办法不同的是,此刻请求的是该类的类目标的Monitor锁。


扩展

上文中针对synchronized的java运用以及字节码做了说明,咱们能够看出synchronized是依靠显式的monitorenter,monitorexit指令和ACC_SYNCHRONIZED完成,但是字节码并不是最靠近机器的一层,相对字节码,汇编又是怎么处理synchronized相关的字节码指令的呢?

咱们能够经过获取java代码的汇编代码来检查,检查Java类的汇编代码需要依靠hsdis工具,该工具能够从chriswhocodes.com/hsdis/下载(科学上网),下载完成后,在Intellij Idea中装备Main类的编译参数如下图所示:

synchronized实现原理

其中vm options具体参数如下:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=compileonly,*SynchronizedTestClass.testSynchronizedBlock -XX:CompileCommand=compileonly,*SynchronizedTestClass.testSynchronizedMethod -XX:+LogCompilation -XX:LogFile=/Volumes/Storage/hotspot.log

其中“compileOnly,”后边跟的是你要抓取的函数称号,格式为:*类名.函数名,LogFile=后指向的是存储汇编代码的文件。

环境变量装备如下:

LIBRARY_PATH=/Volumes/Storage/hsdis

这里的写法是:hsdis存储路径+/hsdis

随后再次运转Main.main即可看到相关汇编代码输出在运转窗口,经过剖析运转窗口输出的内容,咱们能够看到如下截图:

synchronized实现原理

能够看出在运转时调用SynchronizedTestClass::testSynchronizedMethod时,进入synchronized需要履行lock cmpxchg以保证多线程安全,故synchronized的汇编完成为lock cmpxchg指令。

参考链接

/post/684490…

segmentfault.com/a/119000001…