目录
一、什么是synchronized?
synchronized是java里的一个关键字,可以用来给对象和方法或者代码块加锁,当它锁定一个方法或者代码块的时候,同一时刻最多只有一条线程执行这段代,它可以解决并发编程中的三大问题:原子性,可见性和有序性。
二、锁状态
锁状态优先级: 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
1.无锁(01)
在锁对象刚新建时,就是无锁状态。
public class Test01 {
public static void main(String[] args) throws InterruptedException {
Object o =new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
打印结果为:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
特点:修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。也就是CAS(CAS是基于无锁机制实现的)。
2.偏向锁
大部分情况下,锁不仅仅存在多线程竞争,而是总是由一个线程多次获得,为了让线程获取锁的代价更低,就引入了偏向锁。
当一个线程使用synchronized方式获取了一个锁对象时,会在该对象的对象头中存 储当前线程的ID,后续这个线程再次去获取该锁对象时,直接进行线程ID的比较, 成功就表示该锁是偏向当前线程的,不需要再次获取锁了。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。
获取偏向锁:
1、首先获取锁对象头中的 Mark Word,判断当前对象是否处于可偏向状态(即当前没有对象获得偏向锁)。
2、如果是可偏向状态,则通过CAS原子操作,把当前线程的ID写入到 对象头,如果CAS成功,表示获得偏向锁成功,会将偏向锁标记设置为1,且将当前线程的ID写入对象头;如果CAS失败则说明当前有其他线程获得了偏向锁,同时也说明当前环境存在锁竞争,这时候就需要将已获得偏向锁的线程中的偏向锁撤销掉,并升级为轻量级锁。
3、如果当前线程是已偏向状态,需要检查对象头中的ThreadID是否和自己相等,如果相等则不需要再次获得锁,可以直接执行同步代码块,如果不相等,说明当前偏向的是其他线程,需要撤销偏向锁并升级到轻量级锁。
3.轻量级锁(00)
当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
public class Test02 {
public static void main(String[] args) throws InterruptedException {
Object o =new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 f4 ad 03 (11111000 11110100 10101101 00000011) (61732088)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
加锁后的打印结果可以看到,状态位001变成了000,直接由无锁态变成了轻量级锁状态。
4.重量级锁(10)
一个对象当作锁被一个线程所持有,另外的线程还要继续去获取该锁,这时没有获取到锁的线程就会处于阻塞状态等待唤醒,就会变成重量级锁。
总结
轻量级锁(自旋锁)中等待的线程占用cpu,重量级锁等待的线程进入等待队列,不占用cpu。对于执行时间长的线程来说,重量级锁更合适。而自旋锁更适合于当前线程执行时间不长且其他等待线程不多的情况。