0%

synchronized关键字

synchronized的作用

synchronized作为java的一个关键字,用来保证同一时刻最多只有一个线程执行synchronized修饰的方法/代码块,保证线程安全,解决多线程并发问题。

synchronized原理

  • 依赖于JVM

  • 底层通过一个监视器对象(monitor)完成,wait()、notify()等方法也依赖于monitor对象,monitor(监视器锁)本质依赖于底层操作系统的互斥锁(Mutex Lock)实现

早期的JDK中,synchronized是重量级的,需要去操作系统申请,完全依赖于操作系统内部的互斥锁,需要进行用户态到内核态的切换

在JDK1.6进行了锁升级,分别为偏向锁、自旋锁(轻量级锁)、重量级锁

锁升级

在synchronized中,锁存在四种状态,分别为无锁、偏向锁、轻量级锁、重量级锁

偏向锁

偏向锁原理

对于同一个线程多次获得锁,使用偏向锁。一个线程在访问加了synchronized修饰的代码块时,会在对象头中存储当前线程的id,之后该线程进入和退出时,不需要再次加锁和释放锁,而是直接比较对象头里是否存储了指向当前线程的偏向锁

使用偏向锁逻辑

获取偏向锁

  • 首先获取锁对象的Markword,判断是否处于可偏向状态(biased_lock=1、且ThreadId为空)

  • 如果是可偏向状态,则通过CAS操作,把当前线程的ID写入到Markword,如果成功,获得偏向锁,CAS失败,表示当前锁存在竞争,需要撤销已获得偏向锁的线程,升级为轻量级锁

  • 如果是已偏向状态,检查markword中存储的ThreadId是否为当前线程ThreadId,如果不是,说明当前锁已经偏向于其他线程,需要撤销,并升级为轻量级锁

撤销偏向锁

  • 原获得偏向锁的线程如果已经退出了临界区,也就是同步代码块执行完了,这时会把对象头设置成无锁状态并且争抢锁的线程可以基于CAS重新偏向

  • 如果原获得偏向锁的线程的同步代码块还没执行完,处于临界区之内,把原获得偏向锁的线程升级为轻量级锁,继续执行代码块

可以使用jvm参数 UseBiasedLocking来设置是否使用偏向锁

轻量级锁

轻量级锁加锁逻辑
  • 线程在栈帧中创建锁记录LockRecord

  • 将锁对象的对象头中markword复制到线程创建的锁记录中

  • 将锁记录中Owner指针指向锁对象

  • 将锁对象的对象头的markword替换为指向锁记录的指针

轻量级锁在加锁过程中,用到了自旋锁

锁在自旋的时候会消耗cpu,一直在for循环,默认情况下是自旋10次,可以使用jvm参数preBlockSpin来修改

轻量级锁解锁

解锁是获得锁的逆向逻辑,通过CAS操作把线程栈帧中的LockRecord替换到对象的Markword中,如果成功表示没有竞争,如果失败,表示当前锁存在竞争,轻量级锁会成为重量级锁。

重量级锁

重量级基本原路

使用monitorenter/monitorexit获取和释放monitor监视器,使得其它被阻塞的线程可以尝试去获得这个监视器monitor,依赖于操作系统的MutexLock(互斥锁)来实现的,线程被阻塞后进入内核(Linux)调度状态,会导致系统在用户态和内核态之间来回切换,验证影响锁的性能。

synchronized的使用

1
2
3
4
5
6
7
8
锁当前对象  synchronized(this){

}

等价于 锁整个方法
synchronized method(){

}
1
2
3
4
5
6
7
锁当前类 synchronized(T.class){

}
等价于 锁静态方法
synchronized static method(){

}