在《深入理解Java虚拟机》中,有这样一段话:
synchronized关键字在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是“万能”的。的确,大部分并发控制操作都能使用synchronized来完成。
海明威在他的《午后之死》说过的:“冰山运动之雄伟壮观,是因为他只有八分之一在水面上。”
对于程序员来说,synchronized只是个关键字而已,用起来很简单。之所以我们可以在处理多线程问题时可以不用考虑太多,就是因为这个关键字帮我们屏蔽了很多细节。
那么,本文就围绕synchronized展开,主要介绍其用法、原理,以及如何提供原子性、可见性和有序性保障的等。
synchronized的用法
synchronized是Java提供的一个并发控制的关键字。主要有两种用法,分别是同步方法和同步代码块。
也就是说,synchronized既可以修饰方法也可以修饰代码块。代码如下:
/**
* @author Hollis 18/08/04.
*/
publicclassSynchronizedDemo{
//同步方法
publicsynchronizedvoiddoSth(){
System.out.println("Hello World");
}
//同步代码块
publicvoiddoSth1(){
synchronized (SynchronizedDemo.class){
System.out.println("Hello World");
}
}
}
被synchronized修饰的代码块及方法,在同一时间,只能被单个线程访问。
synchronized的实现原理
synchronized,是Java中用于解决并发情况下数据同步访问的一个很重要的关键字。当我们想要保证一个共享资源在同一时间只会被一个线程访问到时,我们可以在代码中使用synchronized关键字对类或者对象加锁。
在深入理解多线程(一)——Synchronized的实现原理中我曾经介绍过其实现原理,为了保证知识的完整性,这里再简单介绍一下,详细的内容请去原文阅读。
我们对上面的代码进行反编译,可以得到如下代码:
publicsynchronizedvoiddoSth();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2// Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3// String Hello World
5: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;)V
8:return
publicvoiddoSth1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #5// class com/hollis/SynchronizedTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #2// Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #3// String Hello World
10: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
15:goto23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23:return
通过反编译后代码可以看出:
对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。
对于同步代码块。JVM采用monitorenter、monitorexit两个指令来实现同步。
在The Java® Virtual Machine Specification中有关于同步方法和同步代码块的实现原理的介绍,我翻译成中文如下:
方法级的同步是隐式的。同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。
同步代码块使用monitorenter和monitorexit两个指令实现。可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。
无论是ACC_SYNCHRONIZED还是monitorenter、monitorexit都是基于Monitor实现的,在Java虚拟机(HotSpot)中,Monitor是基于C++实现的,由ObjectMonitor实现。
作者:Java填坑之路
链接:https://www.jianshu.com/p/e078eee057b7
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章