前言

本文主要内容

  • 介绍Synchronized并进行简单使用
  • 简要介绍Synchronized的实现原理
  • 介绍锁的升降级

正文

介绍及简单使用

介绍

依赖jvm实现的互斥同步方式,被称为内部锁。

重量级锁

因为是互斥同步方式,同一时间只能有一个线程操作共享资源,又因为使用的调度方式是抢占式调度,因此在时间片结束前,正在执行的线程无法将任务执行完。当时间到达,线程挂起,等到下次抢占后,恢复现场,继续工作。一个任务的完成需要频繁的挂起与恢复,而挂起与恢复需要转入内核态完成,这就会在用户态与内核态之间频繁转换,而这种转换会占用大量资源。因此这种实现单一线程操作共享资源的锁被称为重量级锁。在jdk1.6之前,synchronized只有这种方式。

JDK1.6时的优化

首先,重量级锁实现方式并不是不好,在高并发的环境下,这种方式不可替代,但是需要分情况,若竞争同一个共享资源线程数量小于cpu核心数,也就是小于硬件支持的并行线程数量,那么是不是可以考虑下,不让等待的线程挂起?只是让其进行无意义的活动,进行等待;若在某一个时间段,同一个线程连续操作同一个共享资源,那么是否可以考虑在这一时间段取消同步?当作这一个线程独享的moment,直到一个不同的线程开始竞争;对于一个虽然编码阶段是同步资源,但判定它不可能有共享的机会,那么是否应该取消同步?
针对这三个特殊情况,JDK1.6进行了优化,因此有了轻量级锁,偏向锁,无锁

简单使用

内部锁是在对象上加锁。可以修饰静态方法、非静态方法、代码块

  1. public class SynchronizedTest {
  2. private static SynchronizedTest instance;
  3. private int num;
  4. //修饰静态方法,锁类对象
  5. public static synchronized SynchronizedTest getInstance1(){
  6. if(null == instance){
  7. instance = new SynchronizedTest();
  8. }
  9. return instance;
  10. }
  11. //修饰代码块,锁指定对象
  12. public static SynchronizedTest getInstance2(){
  13. if(null == instance){
  14. synchronized (SynchronizedTest.class){
  15. if(null == instance){
  16. instance = new SynchronizedTest();
  17. }
  18. }
  19. }
  20. return instance;
  21. }
  22. //修饰非静态方法,锁当前对象(这个代码纯粹是为了演示)
  23. public synchronized void add(int num){
  24. num++;
  25. }
  26. }

实现原理

预备知识

对象头

image.png

ObjectMonitor

每个对象都有一个与之关联的Monitor对象 -一个synchronized跟面试官扯了半个小时

OpenJDK / jdk8u / jdk8u / hotspot
image.png
这里需要关注的变量有:

  • _count: 重入次数
  • _waiters: 等待线程数
  • _owner: 当前持有锁的线程
  • _WaitSet: 调用了 wait 方法被阻塞的线程
  • _EntryList: 等待获取锁的线程

加锁与释放锁的实现

重量级锁的实现

共享资源对应着一个monitor对象,多个线程竞争共享资源,其实是竞争monitor。请求锁的线程先存入竞争队列中,当线程获取monitor后,
_owner指向当前线程,对象头中的标记字段中记录monitor地址,同时设置锁标志位为10

偏向锁的实现

当锁对象第一次被线程获取时,对象头的标志位记录当前线程ID,若设置成功,则取消同步,直到有其它线程竞争时,取消偏向锁

轻量级锁的实现

若线程数量不超过硬件支持的并行数量,那么其余线程未抢到锁的线程,可以进行自旋来忙等待,以避免频繁的上下文切换。线程会在自己的栈帧中创建锁记录,然后拷贝资源对象的Mark Word,然后将锁记录中的owner指向

在过程中会进行自旋,当超过旋转次数时,就会进行锁升级,升级为重量级锁,避免浪费cpu资源,如果同一个锁上一次成功自旋获取了,那么对于这个锁,下一次自旋的次数就会增加,如失败则相反,这就是自适应自旋