java多线程开发02——synchronized

java多线程开发02——synchronized

  • 偏向锁、轻量级锁是JVM级别实现的
  • 重量级锁是操作系统级别实现的
  • JUC是JDK级别实现的

synchronized应用

  • 对于普通方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是Syschronized括号里配置的对象

对象头

锁的状态通过java对象头进行区分。

syschronized用的锁是存在java对象头里的。

​ ————java并发编程的艺术

以Hotspot虚拟机为例,Hotspot的对象头主要包括两部分数据:Mark Word(标记字段)、Class Pointer(类型指针)。

具体来说是对象头中markword里存储的数据会随着锁标志位的变化而变化。无锁偏向锁的锁标志位都是01,其中偏向锁增加一位标识。轻量级锁的锁标志位是00,重量级锁的标识位位10,GC标记为11。

偏向锁

主要是由于一些变量,整个生命周期里只会被一个线程拥有,如果是轻量级锁,每次进入和退出都需要进行CAS的原子操作,比较耗时

引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。

安全点是在程序执行期间的所有GC Root已知并且所有堆对象的内容一致的点。从全局的角度来看,所有线程必须在GC运行之前在安全点阻塞。 (作为一种特殊情况,运行JNI代码的线程可以继续运行,因为它们只使用句柄。但在安全点期间,它们必须阻塞而不是加载句柄的内容。)从本地的角度来看,安全点是一个显着的点,它位于执行线程可能阻止GC的代码块中。 大多数调用点都能当做安全点。
在每个安全点都存在强大的不变量永远保持true不变,而在非安全点可能会被忽视。 编译的Java代码和C / C ++代码都在安全点之间进行了优化,但跨安全点时却不那么优化。 JIT编译器在每个安全点发出GC映射。 VM中的C / C ++代码使用程式化的基于宏的约定(例如,TRAPS)来标记潜在的安全点。
总的来说,安全点就是指,当线程运行到这类位置时,堆对象状态是确定一致的,JVM可以安全地进行操作,如GC,偏向锁解除等。

偏向锁加锁:当一个线程访问同步代码块并且获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁解锁,只需要简单地测试一下对象头里的markword里是否存储指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下markword中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

  1. 第一次进入的时候在对象头用cas设置线程id
  2. 再进入检测线程id是否指向当前线程id
  3. 是的话进入成功
  4. 失败的话检查是不是偏向锁
  5. 不是偏向锁(说明还是无锁状态)用cas设置锁
  6. 是偏向锁用cas竞争

偏向锁撤销:等待到达全局安全点(在这个时间点没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程处于不活动状态,则将对象头设置成为无锁状态;如果线程仍然或者,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的MarkWord要么重新偏向于其他线程,要么恢复到无锁或者标记对象不合适作为偏向锁,最后唤醒暂停的线程。

  1. 当其他线程竞争时,触发释放锁过程
  2. 等待安全点
  3. 暂停拥有锁的线程,检查它是否活着
  4. 不活跃把对象头设置成为无锁
  5. 活跃的话要么偏向于其他线程,要么恢复到无锁,要么升级成为轻量级锁

总结:偏向锁没有常规意义的解锁,只有等到全局安全点,才能撤销偏向锁的设置。

轻量级锁

轻量级锁加锁

  1. 线程进入代码块之前,在栈帧建立锁记录:在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如下图所示。

    preview

  2. 拷贝对象头中的Mark Word复制到锁记录中。

  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。

  4. 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如下图所示。

  5. 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,若当前只有一个等待线程,则可通过自旋稍微等待一下,可能另一个线程很快就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

轻量级锁解锁

  1. 通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。
  2. 如果替换成功,整个同步过程就完成了。
  3. 如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值