前言
synchronized可以修饰方法和代码块。
synchronized需要持有一个对象锁。分为类锁和对象锁。
反编译
public class Test {
public int count=0;
public synchronized void add(){
count++;
}
public void add1(){
synchronized (this){
count++;
}
}
}
通过javap -v 对class进行反编译
public synchronized void add();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field count:I
5: iconst_1
6: iadd
7: putfield #2 // Field count:I
10: return
当synchronized修饰方法的时候编译器会在flags添加ACC_SYNCHRONIZED
public void add1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_0
5: dup
6: getfield #2 // Field count:I
9: iconst_1
10: iadd
11: putfield #2 // Field count:I
14: aload_1
15: monitorexit
16: goto 24
19: astore_2
20: aload_1
21: monitorexit
22: aload_2
23: athrow
24: return
当synchronized修饰代码块的时候会在代码块中增加monitorenter以及monitorexit指令,也就是说在执行这块代码的时候会获取monit锁,如果执行完之后会将monit释放。
synchronized属于排他锁,当某个线程获取锁之后其他线程就只能阻塞等待锁被释放掉,等待被唤醒。这样会造成两次上下文切换。也就是cpu的调度保存和获取数据。
由于这个问题对synchronized进行了优化:
1.偏向锁
2.适应性自旋锁
3.轻量级锁
偏向锁
大多情况下都是某一个线程去获取锁的时候,当第一次获取锁的时候会将线程的id记录下来,下次如果还是这个线程来获取锁,那他就不需要获取锁了,直接执行代码。如果不是这个线程那么会变成轻量级锁。
轻量级锁
如果内部操作比较简单的时候,线程获取锁和释放锁会在比较短的时间
当有线程获取锁的时候,其他线程来获取锁的时候,不再进行阻塞,让他进行自旋不断尝试获取锁。但是会进行大量的空转,消耗cpu性能,所以规定他自旋的时间不能超过一次上下文切换时间,如果超过了上下文切换时间,就将他挂起,变成重量级锁。
重量级锁
当某个线程获取锁的时候,其他线程再来获取锁会被挂起。
使用场景
使用场景.png
锁对象标记的变化
锁标记的改变.jpg
偏向锁.jpg
轻量级锁