- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章死磕 java同步系列之synchronized解析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
(1)synchronized的特性?
(2)synchronized的实现原理?
(3)synchronized是否可重入?
(4)synchronized是否是公平锁?
(5)synchronized的优化?
(6)synchronized的五种使用方式?
synchronized关键字是java里面最基本的同步手段,它经过编译之后,会在同步块的前后分别生成 monitorenter 和 monitorexit 字节码指令,这两个字节码指令都需要一个引用类型的参数来指明要锁定和解锁的对象.
在学习java内存模型的时候,我们介绍过两个指令:lock 和 unlock.
lock,锁定,作用于主内存的变量,它把主内存中的变量标识为一条线程独占状态.
unlock,解锁,作用于主内存的变量,它把锁定的变量释放出来,释放出来的变量才可以被其它线程锁定.
但是这两个指令并没有直接提供给用户使用,而是提供了两个更高层次的指令 monitorenter 和 monitorexit 来隐式地使用 lock 和 unlock 指令.
而 synchronized 就是使用 monitorenter 和 monitorexit 这两个指令来实现的.
根据jvm规范的要求,在执行monitorenter指令的时候,首先要去尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,就把锁的计数器加1,相应地,在执行monitorexit的时候会把计数器减1,当计数器减小为0时,锁就释放了.
我们还是来上一段代码,看看编译后的字节码长啥样来学习
1
2
3
4
5
6
7
8
9
10
|
public
class
synchronizedtest{
public
static
void
sync(){
synchronized
(synchronizedtest.
class
){
synchronized
(synchronizedtest.
class
){
}
}
}
public
static
void
main(string[] args){
}
}
|
我们这段代码很简单,只是简单地对synchronizedtest.class对象加了两次synchronized,除此之外,啥也没干.
编译后的sync()方法的字节码指令如下,为了便于阅读,彤哥特意加上了注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
// 加载常量池中的synchronizedtest类对象到操作数栈中
0
ldc #
2
<com/coolcoding/code/synchronize/synchronizedtest>
// 复制栈顶元素
2
dup
// 存储一个引用到本地变量0中,后面的0表示第几个变量
3
astore_0
// 调用monitorenter,它的参数变量0,也就是上面的synchronizedtest类对象
4
monitorenter
// 再次加载常量池中的synchronizedtest类对象到操作数栈中
5
ldc #
2
<com/coolcoding/code/synchronize/synchronizedtest>
// 复制栈顶元素
7
dup
// 存储一个引用到本地变量1中
8
astore_1
// 再次调用monitorenter,它的参数是变量1,也还是synchronizedtest类对象
9
monitorenter
// 从本地变量表中加载第1个变量
10
aload_1
// 调用monitorexit解锁,它的参数是上面加载的变量1
11
monitorexit
// 跳到第20行
12
goto
20
(+
8
)
15
astore_2
16
aload_1
17
monitorexit
18
aload_2
19
athrow
// 从本地变量表中加载第0个变量
20
aload_0
// 调用monitorexit解锁,它的参数是上面加载的变量0
21
monitorexit
// 跳到第30行
22
goto
30
(+
8
)
25
astore_326 aload_0
27
monitorexit28 aload_329 athrow
// 方法返回,结束
30
return
|
按照彤哥的注释读起来,字节码比较简单,我们的synchronized锁定的是synchronizedtest类对象,可以看到它从常量池中加载了两次synchronizedtest类对象,分别存储在本地变量0和本地变量1中,解锁的时候正好是相反的顺序,先解锁变量1,再解锁变量0,实际上变量0和变量1指向的是同一个对象,所以synchronized是可重入的.
至于,被加锁的对象具体在对象头中是怎么存储的,彤哥这里就不细讲了,有兴趣的可以看看《java并发编程的艺术》这本书.
前面讲解java内存模型的时候我们说过内存模型主要就是用来解决缓存一致性的问题的,而缓存一致性主要包括原子性、可见性、有序性.
那么,synchronized关键字能否保证这三个特性呢?
还是回到java内存模型上来,synchronized关键字底层是通过monitorenter和monitorexit实现的,而这两个指令又是通过lock和unlock来实现的.
而lock和unlock在java内存模型中是必须满足下面四条规则的:
(1)一个变量同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一个线程执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才能被解锁.
(2)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值; 。
(3)如果一个变量没有被lock操作锁定,则不允许对其执行unlock操作,也不允许unlock一个其它线程锁定的变量; 。
(4)对一个变量执行unlock操作之前,必须先把此变量同步回主内存中,即执行store和write操作; 。
通过规则(1),我们知道对于lock和unlock之间的代码,同一时刻只允许一个线程访问,所以,synchronized是具有原子性的.
通过规则(1)(2)和(4),我们知道每次lock和unlock时都会从主内存加载变量或把变量刷新回主内存,而lock和unlock之间的变量(这里是指锁定的变量)是不会被其它线程修改的,所以,synchronized是具有可见性的.
通过规则(1)和(3),我们知道所有对变量的加锁都要排队进行,且其它线程不允许解锁当前线程锁定的对象,所以,synchronized是具有有序性的.
综上所述,synchronized是可以保证原子性、可见性和有序性的.
通过上面的学习,我们知道了synchronized的实现原理,并且它是可重入的,那么,它是否是公平锁呢?
直接上菜:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
synchronizedtest {
public
static
void
sync(string tips) {
synchronized
(synchronizedtest.
class
) {
system.out.println(tips);
try
{
thread.sleep(
1000
);
}
catch
(interruptedexception e) {
e.printstacktrace();
}
}
}
public
static
void
main(string[] args)
throws
interruptedexception {
new
thread(()->sync(
"线程1"
)).start();
thread.sleep(
100
);
new
thread(()->sync(
"线程2"
)).start();
thread.sleep(
100
);
new
thread(()->sync(
"线程3"
)).start();
thread.sleep(
100
);
new
thread(()->sync(
"线程4"
)).start();
}
}
|
在这段程序中,我们起了四个线程,且分别间隔100ms启动,每个线程里面打印一句话后等待1000ms,如果synchronized是公平锁,那么打印的结果应该依次是 线程1、2、3、4.
但是,实际运行的结果几乎不会出现上面的样子,所以,synchronized是一个非公平锁.
java在不断进化,同样地,java中像synchronized这种古老的东西也在不断进化,比如concurrenthashmap在jdk7的时候还是使用reentrantlock加锁的,在jdk8的时候已经换成了原生的synchronized了,可见synchronized有原生的支持,它的进化空间还是很大的.
那么,synchronized有哪些进化中的状态呢?
我们这里稍做一些简单地介绍:
(1)偏向锁,是指一段同步代码一直被一个线程访问,那么这个线程会自动获取锁,降低获取锁的代价.
(2)轻量级锁,是指当锁是偏向锁时,被另一个线程所访问,偏向锁会升级为轻量级锁,这个线程会通过自旋的方式尝试获取锁,不会阻塞,提高性能.
(3)重量级锁,是指当锁是轻量级锁时,当自旋的线程自旋了一定的次数后,还没有获取到锁,就会进入阻塞状态,该锁升级为重量级锁,重量级锁会使其他线程阻塞,性能降低.
(1)synchronized在编译时会在同步块前后生成monitorenter和monitorexit字节码指令; 。
(2)monitorenter和monitorexit字节码指令需要一个引用类型的参数,基本类型不可以哦; 。
(3)monitorenter和monitorexit字节码指令更底层是使用java内存模型的lock和unlock指令; 。
(4)synchronized是可重入锁; 。
(5)synchronized是非公平锁; 。
(6)synchronized可以同时保证原子性、可见性、有序性; 。
(7)synchronized有三种状态:偏向锁、轻量级锁、重量级锁; 。
通过上面的分析,我们知道synchronized是需要一个引用类型的参数的,而这个引用类型的参数在java中其实可以分成三大类:类对象、实例对象、普通引用,使用方式分别如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
class
synchronizedtest2 {
public
static
final
object lock =
new
object();
// 锁的是synchronizedtest.class对象
public
static
synchronized
void
sync1() {
}
public
static
void
sync2() {
// 锁的是synchronizedtest.class对象
synchronized
(synchronizedtest.
class
) {
}
}
// 锁的是当前实例this
public
synchronized
void
sync3() {
}
public
void
sync4() {
// 锁的是当前实例this
synchronized
(
this
){
}
}
public
void
sync5() {
// 锁的是指定对象lock
synchronized
(lock) {
}
}
}
|
在方法上使用synchronized的时候要注意,会隐式传参,分为静态方法和非静态方法,静态方法上的隐式参数为当前类对象,非静态方法上的隐式参数为当前实例this.
另外,多个synchronized只有锁的是同一个对象,它们之间的代码才是同步的,这一点在使用synchronized的时候一定要注意.
这篇文章就到这里,希望可以给你带来帮助,也希望您可以多多关注我的更多内容! 。
原文链接:https://mp.weixin.qq.com/s/Ur9J10xbw9XSOmXc2Y59Lw 。
最后此篇关于死磕 java同步系列之synchronized解析的文章就讲到这里了,如果你想了解更多关于死磕 java同步系列之synchronized解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
在 java 中不使用“同步”关键字的情况下,是否有其他方法可以同步类或方法? 谢谢, 马利卡琼·科卡塔努尔 最佳答案 您可能想查看并发包中引入的对 JDK 5 的更改。 http://java.su
第 1 部分: 假设下面这段代码 void method1(){ synchronized (lockObject){ method2(); System.ou
我有一个 REST 服务器和一个在移动设备上运行的客户端应用程序。客户端有一些数据并希望从服务器获取数据更新。如何以 RESTful 方式在单个事务中执行此操作? 假设客户有以下元素: widge
我有一个多线程 Java 应用程序。在一种方法中,需要同步一个 ArrayList。由于 arrayList 不是线程安全的,所以我必须使用同步。问题是 ArrayList 类型的对象不是对象的成员变
我正在阅读 Android 示例中的 BluetoothChatService.java 文件,有一件事特别让我感到困惑。 方法在多个位置访问静态成员,并且定义为同步。 在另一部分中,正在访问同一个静
我知道为了实现线程安全和同步,我们使用同步块(synchronized block)或方法。 但我无法理解声明- “Java 中的同步块(synchronized block)在某些对象上同步 ” 任
在 Scala 中使用 JDBC 的示例中,有以下代码: this.synchronized { if (!driverLoaded) loadDriver() } 为什么this.synchro
abstract class A { protected abstract int isRunning(); public void concreteMethod() { synchr
有谁可以分享一下他们的经验吗?“我们什么时候在同步方法和同步块(synchronized block)之间进行调用”有任何性能问题吗? 最佳答案 When do we make a call to u
这是我之前问题的后续问题,Is this variable being safely accessed by using synchronization? 对于下面的程序, Class SubClas
我目前正在为 N 体问题实现多线程版本的 Barnes-Hut 算法。虽然该算法有效,但它不是很优化,我正在尝试减少我的程序的运行时间。 我已经确保有多个线程可以准确地找到我正在使用的空间的边界,并意
我有这门课: public class MyClass { public MyClass(){} public void actionA(){ synchronized
又是一个关于ArrayList和synchronize的问题。 我只想知道这段代码到底做了什么: ArrayList list = ....; synchronized (list) { if
我可以在另一个同步块(synchronized block)中包含同步块(synchronized block)以同步另一个对象吗? 例子: synchronized(myObjetc1){
public class ObjectCounter { private static long numOfInstances = 0; public ObjectCounter(){
我在某处读到,对于 various reasons 应该避免 synchronized(this) .然而,我遇到的一些值得尊敬的代码在构造函数中使用了以下内容: public SomeClass(C
Java 为同步代码的关键部分提供了一种非常方便的习惯用法: synchronized(someObject) { // do something really important all b
我有一个 WeakReference 的 Collections.synchronizedList,_components; 我写了类似下面的内容,希望编译者会提示: public boolean a
使用下面两个版本的Singleton Classes有什么区别 首先我使用的是synchronized(Singleton.class) 在第二个我使用同步(Obj)//第一种类型 公共(public
我正在查看 DatagramSocket 的源代码,我发现了这个: public void disconnect() { synchronized (this) { if (i
我是一名优秀的程序员,十分优秀!