相信对于各位java大佬,synchronized关键字大家可能并不陌生,今天这边来详细聊聊这个synchronized关键字。
相信有有写java并发基础的同学可能知道,synchronized关键字是利用锁的机制来实现代码同步的。对于java中的锁机制有如下两个特性(还是要了解下的,吹牛逼的时候好提升下b格)
互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
关于synchronized关键字,有如下几种用法:
根据修饰对象分类
1、同步方法
(1) 同步非静态方法
Public synchronized void methodName(){
……
}
(2) 同步静态方法
Public synchronized static void methodName(){
……
}
2、同步代码块
synchronized(this|object) {}
synchronized(类.class) {}
Private final Object MUTEX =new Object();
Public void methodName(){
Synchronized(MUTEX ){
……
}
}
关于这两种方式具体有什么区别呢?
在说这个之前,我们先来讲讲对象锁和类锁:
(1)获取对象锁(修饰非静态方法):synchronized(this|object) {}
在 Java 中,每个对象都有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
(2)获取类锁(修饰静态方法):synchronized(类.class) {}
在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。
在 Java 中,每个对象都会有一个 monitor 对象,监视器。
-
某一线程占有这个对象的时候,先monitor 的计数器是不是0,如果是0还没有线程占有,这个时候线程占有这个对象,并且对这个对象的monitor+1;如果不为0,表示这个线程已经被其他线程占有,这个线程等待。当线程释放占有权的时候,monitor-1;
-
同一线程可以对同一对象进行多次加锁,+1,+1,重入性,因此synchronized也可以理解为一把可重入锁
下面我么便来验证下上面的,这里介绍一个java反编译命令:javap -v
baomw@baomingwudeMacBook-Air:~/IdeaProjects/test1/target/classes/com/baomw$ javap -v SyncTest.class
Classfile /Users/baomw/IdeaProjects/test1/target/classes/com/baomw/SyncTest.class
Last modified 2019-3-17; size 763 bytes
MD5 checksum d728833a93ce9cf96516976dd96d5ed7
Compiled from "SyncTest.java"
public class com.baomw.SyncTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#29 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#30 // com/baomw/SyncTest.num:I
#3 = Methodref #4.#31 // com/baomw/SyncTest.add:()V
#4 = Class #32 // com/baomw/SyncTest
#5 = Class #33 // java/lang/Object
#6 = Utf8 num
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lcom/baomw/SyncTest;
#15 = Utf8 add
#16 = Utf8 remove
#17 = Utf8 StackMapTable
#18 = Class #32 // com/baomw/SyncTest
#19 = Class #33 // java/lang/Object
#20 = Class #34 // java/lang/Throwable
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 MethodParameters
#26 = Utf8 <clinit>
#27 = Utf8 SourceFile
#28 = Utf8 SyncTest.java
#29 = NameAndType #8:#9 // "<init>":()V
#30 = NameAndType #6:#7 // num:I
#31 = NameAndType #15:#9 // add:()V
#32 = Utf8 com/baomw/SyncTest
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/Throwable
{
public com.baomw.SyncTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/baomw/SyncTest;
public static synchronized void add();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field num:I
3: iconst_1
4: iadd
5: putstatic #2 // Field num:I
8: return
LineNumberTable:
line 18: 0
line 19: 8
public void remove();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field num:I
7: iconst_1
8: isub
9: putstatic #2 // Field num:I
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 23: 0
line 24: 4
line 25: 12
line 26: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this Lcom/baomw/SyncTest;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/baomw/SyncTest, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #3 // Method add:()V
3: return
LineNumberTable:
line 29: 0
line 30: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_0
1: putstatic #2 // Field num:I
4: return
LineNumberTable:
line 15: 0
}
SourceFile: "SyncTest.java"
测试代码发编译后的指令如上:
如上,在计算机指令中,当synchronized修饰我们的方法时,在方法入口增加ACC_SYNCHRONIZED 对整个方法加锁
而在修饰我们的代码块时,在代码块的执行入口和出口处增加Monitorenter和
Monitorexit,注意这两个总是成对出现的。
当然最后在使用我们的synchronized的时候,以下问题还是需要注意下的:
(1)与moniter关联的对象不能为空
这个应该不用多说,因为我们的每一个java对象都和一个monitor关联,既然对象都为空了,monitor也就无从谈起了。
(2)synchronized作用域太大
在使用中应该尽可能减少锁的粒度,不然作用域太大,作用域内的代码很可能还会出现并发问题,其次,代码都变串行执行,效率低
(3)不同的monitor企图锁相同的方法
(4)多个锁的交叉导致死锁
好了关于synchronized关键子暂时先讲到这里,后续有相关知识再来补充。