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();
}
});
}
运转结果如下:
从上图能够看出在线程2获取锁前,mLock处于无锁状况,等线程2获取锁后,mLock目标晋级为轻量级锁,等线程1获取锁后晋级为重量级锁,有同学要问了,你在多线程与锁中不是说了synchronized锁晋级有四个吗?你是不是写BUG了,当然没有啊,现在咱们来看看倾向锁去哪儿了?
倾向锁
关于不同版本的JDK而言,其针对倾向锁的开关和装备均有所不同,咱们能够经过履行java -XX:+PrintFlagsFinal -version | grep BiasedLocking
来获取倾向锁相关装备,履行命令输出如下:
从上图能够看出在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();
}
});
}
输出如下:
从上图能够看出在延迟5s履行后,mLock锁变成了无锁可倾向状况,结合上面两个示例,咱们能够看出,在轻量级锁和倾向锁阶段均有可能直接晋级成重量级锁,是否晋级依靠于当时的锁竞赛联系,据此咱们能够得到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();
}
});
}
运转结果如下:
能够看出,在调用同步办法时,直接晋级为重量级锁,同一时间,有且仅有一个线程在同步办法中履行,其他函数在同步办法入口处堵塞等候。
静态同步办法
运用线程池调用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();
}
});
}
运转结果如下:
能够看出,在调用静态同步办法时,直接晋级为重量级锁,同一时间,有且仅有一个线程在静态同步办法中履行,其他函数在同步办法入口处堵塞等候。
前面咱们看的是多个线程竞赛同一个锁目标,那么假定咱们有三个线程别离履行这三个函数,又会怎样呢?代码如下:
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();
}
});
}
运转结果:
能够看到,3个线程各自运转,互不影响,这也进一步印证了前文所说的锁目标以及MarkWord中符号锁状况的概念。
synchronized完成原理
上面已经学习了synchronized的常见用法,关联的锁目标以及锁晋级的进程,接下来咱们来看下synchronized完成原理,仍然以上面的SynchronizedTestClass为例,检查其生成的字节码来了解synchronized关键字的完成。
同步代码块
testSynchronizedBlock其所对应的字节码如下图所示:
从上图代码和字节码对应联系能够看出,在同步代码块中获取锁时运用monitorenter指令,开释锁时运用monitorexit指令,且会有两个monitorexit,保证在当时线程异常时,锁正常开释,防止其他线程等候死锁。
所以synchronized的同步机制是依靠monitorenter和monitorexit指令完成的,而这两个指令操作的便是mLock目标的monitor锁,monitorenter尝试获取mLock的monitor锁,如果获取成功,则monitor中的计数器+1,同时记录相关线程信息,如果获取失利,则当时线程堵塞。
Monitor锁便是存储在MarkWord中的指向重量级锁的指针所指向的目标,每个目标在构造时都会创建一个Monitor锁,用于监督当时目标的锁状况以及持锁线程信息,
同步办法
testSynchronizedMethod其所对应的字节码如下图所示:
能够看到同步办法依靠在函数声明时添加ACC_SYNCHRONIZED符号完成,在函数被ACC_SYNCHRONIZED润饰时,调用该函数会请求目标的Monitor锁,请求成功则进入函数,请求失利则堵塞当时线程。
静态同步办法
testSynchronizedStaticMethod其所对应的字节码如下图所示:
和同步办法相同,同步静态办法也是在函数声明部分添加了ACC_SYNCHRONIZED符号,也同步办法不同的是,此刻请求的是该类的类目标的Monitor锁。
扩展
上文中针对synchronized的java运用以及字节码做了说明,咱们能够看出synchronized是依靠显式的monitorenter,monitorexit指令和ACC_SYNCHRONIZED完成,但是字节码并不是最靠近机器的一层,相对字节码,汇编又是怎么处理synchronized相关的字节码指令的呢?
咱们能够经过获取java代码的汇编代码来检查,检查Java类的汇编代码需要依靠hsdis工具,该工具能够从chriswhocodes.com/hsdis/下载(科学上网),下载完成后,在Intellij Idea中装备Main类的编译参数如下图所示:
其中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即可看到相关汇编代码输出在运转窗口,经过剖析运转窗口输出的内容,咱们能够看到如下截图:
能够看出在运转时调用SynchronizedTestClass::testSynchronizedMethod时,进入synchronized需要履行lock cmpxchg以保证多线程安全,故synchronized的汇编完成为lock cmpxchg指令。
参考链接
/post/684490…
segmentfault.com/a/119000001…