bilibili-Java并发学习笔记3 synchronized
基于 java 1.8.0
P11_synchronized关键字原理详解
P12_透过字节码理解synchronized关键字
- 对当前类实例上锁
package new_package.thread.p12;
public class SynchronizedTest2 {
public synchronized void hello() {
int i = 18;
}
public void world() {
synchronized (this) {
int i = 19;
}
}
}
- 对当前class类上锁
package new_package.thread.p12;
public class SynchronizedTest4 {
public static synchronized void hello() {
String sync = "static";
}
public void world() {
synchronized (SynchronizedTest4.class) {
String sync = "xxx";
}
}
}
- 对类中变量加锁
package new_package.thread.p12;
public class SynchronizedTest6 {
Object obj = new Object();
public void hello() {
synchronized (obj) {
String xxx = "Object";
}
}
public void world() {
synchronized (obj) {
String str2 = "world";
throw new RuntimeException("my exception");
}
}
}
- javap -v new_package.thread.p12.SynchronizedTest2
{
public synchronized void hello();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=1, locals=2, args_size=1
0: bipush 18
2: istore_1
3: return
LineNumberTable:
line 6: 0
line 7: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lnew_package/thread/p12/SynchronizedTest2;
3 1 1 i I
public void world();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: bipush 19
6: istore_2
7: aload_1
8: monitorexit
9: goto 17
12: astore_3
13: aload_1
14: monitorexit
15: aload_3
16: athrow
17: return
Exception table:
from to target type
4 9 12 any
12 15 12 any
LineNumberTable:
line 10: 0
line 11: 4
line 12: 7
line 13: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lnew_package/thread/p12/SynchronizedTest2;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class new_package/thread/p12/SynchronizedTest2, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
解析1 :
- 对于 synchronized 关键字修饰方法来说,并没有 monitorenter 和 monitorexit 指令,而是出现了一个
ACC_SYNCHRONIZED
标识 - JVM 使用
ACC_SYNCHRONIZED
标识来区分一个方法是否为同步方法
;如果是,执行线程会先持有方法所在对象的Monitor 对象
,然后再去执行方法体,线程执行完后(正常执行完返回,发生异常退出方法)释放Monitor 对象
; - 线程持有
Monitor 对象
期间,其他线程将无法再获取当前对象的Monitor 对象
;
- javap -v new_package.thread.p12.SynchronizedTest4
{
public new_package.thread.p12.SynchronizedTest4();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lnew_package/thread/p12/SynchronizedTest4;
public static synchronized void hello();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=1, locals=1, args_size=0
0: ldc #2 // String static
2: astore_0
3: return
LineNumberTable:
line 5: 0
line 6: 3
LocalVariableTable:
Start Length Slot Name Signature
3 1 0 sync Ljava/lang/String;
public void world();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: ldc #3 // class new_package/thread/p12/SynchronizedTest4
2: dup
3: astore_1
4: monitorenter
5: ldc #4 // String xxx
7: astore_2
8: aload_1
9: monitorexit
10: goto 18
13: astore_3
14: aload_1
15: monitorexit
16: aload_3
17: athrow
18: return
Exception table:
from to target type
5 10 13 any
13 16 13 any
LineNumberTable:
line 9: 0
line 10: 5
line 11: 8
line 12: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 this Lnew_package/thread/p12/SynchronizedTest4;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class new_package/thread/p12/SynchronizedTest4, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
解析 2 :
- 静态同步方法,同时使用 ACC_STATIC, ACC_SYNCHRONIZED 两个标识
- 线程执行时,首先获取当前
Class 类对象
(注意区别于实例对象)的 Monitor 对象,…
- javap -v new_package.thread.p12.SynchronizedTest6
{
java.lang.Object obj;
descriptor: Ljava/lang/Object;
flags:
public new_package.thread.p12.SynchronizedTest6();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field obj:Ljava/lang/Object;
15: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Lnew_package/thread/p12/SynchronizedTest6;
public void hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: ldc #4 // String Object
9: astore_2
10: aload_1
11: monitorexit
12: goto 20
15: astore_3
16: aload_1
17: monitorexit
18: aload_3
19: athrow
20: return
Exception table:
from to target type
7 12 15 any
15 18 15 any
LineNumberTable:
line 8: 0
line 9: 7
line 10: 10
line 11: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 this Lnew_package/thread/p12/SynchronizedTest6;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 15
locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public void world();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: ldc #5 // String world
9: astore_2
10: new #6 // class java/lang/RuntimeException
13: dup
14: ldc #7 // String my exception
16: invokespecial #8 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
19: athrow
20: astore_3
21: aload_1
22: monitorexit
23: aload_3
24: athrow
Exception table:
from to target type
7 23 20 any
LineNumberTable:
line 14: 0
line 15: 7
line 16: 10
line 17: 20
LocalVariableTable:
Start Length Slot Name Signature
10 10 2 str2 Ljava/lang/String;
0 25 0 this Lnew_package/thread/p12/SynchronizedTest6;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 20
locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
}
解析3 :
- synchronized 代码块,字节码层次上是通过 monitorenter 和 monitorexit 指令来实现
获取锁
和释放锁
; - 当线程进入到 monitorenter 指令后,线程将会持有 Monitor 对象,退出 monitorenter 指令后,线程将会释放 Monitor 对象;
- hello 方法有两种结束方式:正常执行,异常结束,所以退出路径有两个,有两个 monitorexit 指令
- world 方法主动抛出异常,所以没有正常执行结束流程,只有一个 monitorexit 指令
P14_自旋对于synchronized关键字的底层意义与价值分析
- JVM 的同步是基于进入与退出
监视器对象
(管程对象Monitor
)来实现的,每个对象实例都会有一个 Monitor 对象,Monitor 对象会和 java 对象一同创建和销毁,Monitor 对象是由 C++ 来实现的。 - 当多个线程同时访问同一段同步代码时,这些线程会被放到一个
EntryList 集合
中(处于阻塞状态的线程也会在该列表中);当某一线程获取到对象的 Monitor 对象时,Monitor 依赖于底层操作系统的mutex lock
来实现互斥,线程获取mutex
成功,则会持有该mutex
,这时其他线程就无法再获取到该 mutex 。 - 如果线程调用了 wait 方法,那么该线程就会释放所持有的 mutex,并且该线程会进入到
WaieSet 集合
(等待集合)中,等待下一次被其他线程调用 notify/notifyAll 唤醒。如果当前线程执行返回,也会释放所持有的 mutex 。 - 同步锁在这种实现方式当中,因为 Monitor 是依赖于底层的操作系统实现,这样就存在
用户态
和内核态
之间的切换,所以会增加性能开销
。 - 通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应一个可称为
互斥锁
的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。 - 那些处于 EntryList 和 WaitSet 中的线程均处于阻塞状态,阻塞操作是由操作系统完成的,在 Linux 系统下是通过
pthread_mutex_lock 函数
实现的。线程被阻塞后便会进入到内核调度
状态,这会导致系统在用户态与内核态之间来回切换,严重影响锁的性能。 - 解决上述问题的办法便是
自旋
。其原理是:当发生对 Monitor 的争抢时,若Owner(持有 Monitor 的线程)
能够在很短时间内释放掉锁,则那些正在争抢锁的线程就可以稍微等待一下(即所谓的自旋),在 Owner 线程释放锁之后,自旋的线程可能会立即获取到锁,从而避免阻塞。不过,当 Owner 线程运行时间超过临界值后,自旋线程自旋一段时间依然无法获取到锁,这时自旋线程则会停止自旋而进入到阻塞状态。所以总体思想是:先自旋,不成功再进行阻塞,尽量降低阻塞的可能性
,这对那些执行时间很短的代码块来说有极大的性能提升。显然,自旋(需要消耗 CPU 性能)在多处理器(多核心)上才有意义。
P15_互斥锁属性详解与Monitor对象特性解说
互斥锁的属性:
- PTHREAD_MUTEX_TIMED_NP
属性缺省值
,普通锁
: 当一个线程加锁之后,其余请求锁的线程将会形成一个等待队列,并且在解锁后按照优先级获取到锁,这种策略可以确保资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP
嵌套锁
: 允许一个线程对同一个锁成功获取多次
,并通过 unlock 解锁。如果是不同线程请求,则在加锁线程解锁时重新进行竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP
检错锁
: 如果一个线程请求同一个锁,则返回 EDEADLK
,否则与 PTHREAD_MUTEX_TIMED_NP 类型动作相同,这样就保证了当不允许多次加锁时不会出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP
适应锁
: 动作最简单的锁,仅仅等待解锁后重新竞争。