【Java】【并发编程】Synchronized
在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能)。
特点
- 保证原子性:通过地城JVM实现,同一时间只有一个线程运行锁定代码块
- 保证可见性,通过内存屏障(Load&store)实现,通过monitorenter加锁,monitorexit解锁,内部共享变量每次读取都强制从主内存读取最新值,释放锁后共享变量数据变更,强制刷新会主内存
- 保证有序性:monitorenter加锁之后,同一时间只能被一个线程访问,即认为单线程执行,可以保证有序性,但无法禁止指令重排
- 无法禁止指令重排:在代码块中,相当于单线程执行,单代码块的执行顺序可以指令重排
- 通过Monitor管程监视器锁对象或ACC_SYNCHRONIZED修饰符实现加锁
- monitorenter指令加锁,计数器+1
- monitorexit指令释放锁,计数器-1
- 线程解锁前,必须把共享变量的最新值刷新到主内存。
- 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意,加锁与解锁是同一把锁)
实现(锁范围)
- 修饰普通方法,当前对象锁
- 修饰静态方法,类锁,所有类对象共用同一把锁
- 修饰实例变量,仅对指定的对象加锁
// 只能对不可变的共享对象加锁
private final Object demo = new Demo();
public synchronized void addNumber(long num){ // 对当前方法加锁,等同synchronized(this){}
demo.count += num;
}
public static synchronized void addNumber_1(long num){ // 对类下所有对象加锁
demo.count += num;
}
public void addNumber2(long num){
synchronized (demo){ // 仅对demo加锁
demo.count += num;
}
}
原理
- 同步实例,使用monitor管程对象管理,monitorenter加锁和monitorexit释放锁
- 同步方式,使用运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的
- 静态方法还需要ACC_STATIC标志
修饰实例
private final Demo DEMO = new Demo();
public void test(){
synchronized (DEMO){
DEMO.test1(); // test1是Demo的普通方法,无需关注
}
}
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #4 // Field DEMO:Lcom/dongle/demo/jvm/lock/LockDemo$Demo;
4: dup
5: astore_1
6: monitorenter
7: aload_0
8: getfield #4 // Field DEMO:Lcom/dongle/demo/jvm/lock/LockDemo$Demo;
11: invokestatic #5 // Method com/dongle/demo/jvm/lock/LockDemo$Demo.access$100:(Lcom/dongle/demo/jvm/lock/LockDemo$Demo;)V
14: aload_1
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
Exception table:
from to target type
7 16 19 any
19 22 19 any
LineNumberTable:
line 13: 0
line 14: 7
line 15: 14
line 16: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 this Lcom/dongle/demo/jvm/lock/LockDemo;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class com/dongle/demo/jvm/lock/LockDemo, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
- 通过monitorenter加锁:
6: monitorenter
- 通过monitorexit释放锁:
15: monitorexit
修饰方法
private final Demo DEMO = new Demo();
public synchronized void test(){
DEMO.test1(); // test1是Demo的普通方法,无需关注
}
public synchronized void test();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #4 // Field DEMO:Lcom/dongle/demo/jvm/lock/LockDemo$Demo;
4: invokestatic #5 // Method com/dongle/demo/jvm/lock/LockDemo$Demo.access$100:(Lcom/dongle/demo/jvm/lock/LockDemo$Demo;)V
7: return
LineNumberTable:
line 13: 0
line 14: 7
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this Lcom/dongle/demo/jvm/lock/LockDemo;
- 通过方法修饰符
ACC_SYNCHRONIZED
隐式实现