Synchronized底层实现
synchronized关键字锁的都是对象,不是代码。
synchronized可以修饰静态方法、成员函数,同时还可以直接定义代码块,但是它上锁的资源只有两类:一个是对象,一个是类。
-
对象锁和类锁
对象锁:只作用于同一个对象,如果调用两个同一类的对象上的同步代码块,就不会同步。
类锁:作用于当前整个类,如果两个线程调用同一个类的不同对象上的同步语句,实惠同步的。 -
等待池和锁池
等待池:某一现场调用了某一对象的wait()方法之后,档期啊线程就会释放该对象的锁,进入到该对象的等待池中。
锁池:获取某一对象的monitor lock,如果当前对象的monitor lock呗其他线程所获取了,当前没有获取到该对象monitor lock的线程就会进入当前对象的锁池中。 -
Synchronized关键字同步方法的底层原理
在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。
public class Test1 {
public int i;
public synchronized void myMethod() {
i++;
}
}
相应字节码
{
public int i;
descriptor: I
flags: ACC_PUBLIC
public com.myOwn.demo.Test1();
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
public synchronized void myMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 8: 0
line 9: 10
}
synchronized修饰的方法并没有monitorenter指令和monitorexit指令,而是有一个ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。这就是synchronized锁在同步方法上实现的基本原理。
- Synchronized关键字同步代码块的底层原理
public class Test2 {
public static void main(String[] args) {
synchronized(MySync3.class) {
System.out.println("MySync3");
}
}
}
相应字节码
public class com.myOwn.demo.Test2 {
public com.myOwn.demo.Test2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // class com/myOwn/demo/MySync3
2: dup
3: astore_1
4: monitorenter //注意此处有monitorenter指令
5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #4 // String MySync3
10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit //注意此处有monitorexit指令
15: goto 23
18: astore_2
19: aload_1
20: monitorexit //注意此处有monitorexit指令
21: aload_2
22: athrow
23: return
Exception table:
from to target type
5 15 18 any
18 21 18 any
}
同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。
- Synchronized锁升级(偏向锁、轻量级锁、重量级锁)
1.锁的状态:
a.无锁状态
b.偏向锁状态
c.轻量级锁状态
d.重量级锁状态
注意:四种状态会随着竞争的情况逐渐升级,升级之后不可逆,即不可降级。
2.偏向锁
a.偏向锁的使用:测试对象头Mark Word里是否存储着指向当前线程的偏向锁。若测试失败,则测试Mark Word中偏向锁标识是否设置成1(表示当前为偏向锁),没有设置则使用CAS竞争,否则尝试使用CAS将对象头的偏向锁指向当前线程。
b.偏向锁的升级:当有第二个线程进入同步代码块时,则升级为轻量级锁。
3.轻量级锁
a.轻量级锁的加锁:如果成功使用CAS将对象头重的Mark Word替换为指向锁记录的指针,则获得锁,失败则当前线程尝试使用自旋(循环等待)来获取锁。
b.轻量级锁的解锁:当有另一个线程与该线程同时竞争时,锁会升级为重量级锁。为了防止继续自旋,一旦升级,将无法降级。
4.重量级锁
重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程,进行竞争。