synchronized底层的锁大致有以下这四类:
锁状态 | 是否为偏向锁 | 锁标志 |
无锁不可偏向 | 01 | |
无锁可偏向 | 1 | 01 |
轻量级锁 | 00 | |
重量级锁 | 10 |
当没有开启偏向锁测试时:
一个对象如果没有被任何线程当作锁去使用,此时就是无锁状态。(01)
一个对象如果被一个线程当作锁去使用,此时就是轻量级锁状态。(00)
一个对象当作锁被一个线程持有,另外一个线程还继续去获取该锁,此时就是重量级锁状态。(10)
1.偏向锁:
大部分情况下,锁不仅仅不存在多线程竞争,而是总是由同一个线程多次获得,为了让线程获取锁的代价更低时,这才引入了偏向锁的概念。
当一个线程访问了同步锁的代码块时,会在对象头中存储当前线程的id。
后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁,而是直接比较对象头里面是否含有存储了指向当前线程的偏向锁。
如果相等表示偏向锁是偏向了当前线程的,就不需要再次尝试获取锁了。
2.无锁:
如果锁状态为偏向锁状态,有第二个线程访问这个对象出现线程竞争;(偏向锁不会主动释放锁)操作系统检测原来只有该线程的锁状态,
如果不存活;修改为无锁状态,重新指向新的线程;
public static void main(String[] args) throws InterruptedException {
A a = new A();
String str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//01 无锁状态
new Thread(){
public void run() {
//把a作为锁对象
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
}
}
从图中我们能看到value一栏中显示(01),说明此时是无锁状态
3.轻量级锁:
轻量级锁是相对于使用操作系统互斥量来实现的传统锁而言的。轻量级锁并不是用来代替重量级锁的,它是指在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况。
轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
CAS:(compare and swap)
a.在初始化数组的时候,通过CAS保证只会创建出一个数组对象
b.在新增数组时,两个线程算出来索引值位置都是i,新增的时候会有一个CAS的判断,只能有一个线程新增成功
如果新增失败,那么线程会继续循环,进入下一次的新增判断
public class test01 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
String str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//01 无锁状态
new Thread(){
public void run() {
//把a作为锁对象
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//00 轻量级锁
}
}
运行后能看到为轻量级锁(00)
4.重量级锁:
如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。
互斥锁(sleep-waiting):lock synchronized
public static void main(String[] args) throws InterruptedException {
A a = new A();
String str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//01 无锁状态
new Thread(){
public void run() {
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//00 轻量级锁
new Thread(){
public void run() {
synchronized(a) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
Thread.sleep(10);
str = ClassLayout.parseInstance(a).toPrintable();
System.out.println(str);//10 重量级锁
}
}
运行后显示为重量级锁 (10)
总结:
锁状态 | 优点 | 缺点 | 使用场景 |
偏向锁 | 加锁解锁不需要额外的消耗 | 如果竞争的线程多,那么会带来额外的锁消耗 | 基本无线程争抢的时候 |
轻量级锁 | 竞争的线程不会阻塞,使用自旋,提高程序响应速度 | 如果一直不能获取锁,长时间自旋会造成cpu的消耗 | 如果一直不能获取锁,长时间自旋会造成cpu的消耗 |
重量级锁 | 线程竞争不适用cpu自旋,不会导致cpu空转消耗cpu资源 | 线程阻塞,响应时间长 | 很多线程竞争锁,且锁持有时间长 |