纯属记录个人理解,有误请指出
一、Synchronized加锁方式:
1)同步实例方法,锁是当前实例对象
public synchronized void method() {
/*修饰实例方法,锁的是当前实例对象*/
}
synchronized(this) {
/*修饰同步代码块,锁的是当前实例对象*/
}
2)同步类方法,锁是该类类对象
public static synchronized void method() {
/*修饰静态方法,锁的是该类类对象*/
}
synchronized(Demo.class) {
/*修饰同步代码块,锁的是该类类对象*/
}
3)同步代码块,锁是括号里面的对象
String object = "";
synchronized(object) {
/*修饰同步代码块,锁的是实例对象Object*/
}
二、Synchronized特性:
1)互斥性:Synchronized修饰的同步代码块、实例方法、静态方法,多线程并发访问时,只能有一个线程获取到锁,为非公平锁
,每个刚来的线程都会尝试下插队,如果插不了才进入等待队列
,进入阻塞状态
等待。
2)可见性:某线程 A 对于进入同步块之前或在 synchronized 中对于共享变量的操作,对于后续的持有同一个监视器锁的其他线程可见
同步方法和同步代码块底层都是通过
monitor
来实现同步的。每个对象都与一个monitor相关联。同步方法是通过方法中的access_flags中设置
ACC_SYNCHRONIZED标志
来实现;同步代码块是通过monitorenter和monitorexit来实现
。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”
两个态之间来回切换,对性能有较大影响。
三、查看ACC_SYNCHRONIZED标志
查看字节码在终端操作的命令为
javac SyncDemo.java
javap -c -verbose SyncDemo
public class SyncDemo {
public static int sum = 0;
public static synchronized void addSum() {
sum++;
System.out.println(sum);
}
public static void main(String[] args) {
new Thread("a") {
public void run() {
addSum();
}
}.start();
new Thread("b") {
public void run() {
addSum();
}
}.start();
}
}
查看结果:我们可以看到在flags中有 ACC_SYNCHRONIZED
标志
public static synchronized void addSum();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #11 // Field sum:I
3: iconst_1
4: iadd
5: putstatic #11 // Field sum:I
8: return
LineNumberTable:
line 20: 0
line 21: 8
四、查看同步代码块的monitor
public class SyncDemo {
public static int sum = 0;
public static void addSum() {
synchronized (SyncDemo.class) {
sum++;
System.out.println(sum);
}
}
static SyncDemo syncDemo;
public static void main(String[] args) {
new Thread("a") {
public void run() {
addSum();
}
}.start();
new Thread("b") {
public void run() {
addSum();
}
}.start();
}
}
查看结果:我们可以看到第9行monitorenter加锁
,第23行monitorexit释放锁
,第29行monitorexit为发生异常释放锁
public static void addSum();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: ldc #2 // class pang/one/SyncDemo
2: dup
3: astore_0
4: monitorenter
5: getstatic #3 // Field sum:I
8: iconst_1
9: iadd
10: putstatic #3 // Field sum:I
13: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
16: getstatic #3 // Field sum:I
19: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
22: aload_0
23: monitorexit
24: goto 32
27: astore_1
28: aload_0
29: monitorexit
30: aload_1
31: athrow
32: return
Exception table:
from to target type
5 24 27 any
27 30 27 any
LineNumberTable:
line 7: 0
line 8: 5
line 9: 13
line 10: 22
line 11: 32
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 27
locals = [ class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
五、查看monitor中的锁级别
锁级别有:
锁状态 | 1bit(是否偏向锁) | 2bit(锁标志位) |
---|---|---|
无锁态 | 0 | 01 |
轻量级锁 | 0 | 00 |
重量级锁 | 0 | 10 |
GC标记 | 0 | 11 |
偏向锁 | 1 | 01 |
没加锁
import org.openjdk.jol.info.ClassLayout;
public class SyncDemo {
static SyncDemo syncDemo;
public static void main(String[] args) {
syncDemo = new SyncDemo();
//synchronized (syncDemo) {
//System.out.println("locking");
syncDemo.hashCode();
System.out.println(ClassLayout.parseInstance(syncDemo).toPrintable());
//}
}
}
输出结果:无锁态
加锁之后
import org.openjdk.jol.info.ClassLayout;
public class SyncDemo {
static SyncDemo syncDemo;
public static void main(String[] args) {
syncDemo = new SyncDemo();
synchronized (syncDemo) {
System.out.println("locking");
syncDemo.hashCode();
System.out.println(ClassLayout.parseInstance(syncDemo).toPrintable());
}
}
}
输出结果:重量级锁
六、Synchronized锁升级过程
1、在
无锁
状态,线程1访问同步块进行CAS修改MarkWord,进入偏向锁
状态,因为偏向锁是在单线程的情况
; 2、当进行另一个线程2访问同步块时,CAS修改MarkWord失败,开始撤销偏向锁,升级为
轻量级锁
; 3、而在线程2继续访问同步块进行CAS修改MarkWord时,
自旋
获取锁失败一定次数
后,锁膨胀,升级为重量级锁
,从而线程阻塞; 4、直到等待线程1
释放锁
,唤醒
阻塞的线程,开始新的一轮锁竞争。