前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >悲观锁

悲观锁

作者头像
shysh95
发布2021-05-11 15:17:29
2870
发布2021-05-11 15:17:29
举报
文章被收录于专栏:shysh95shysh95

Hi~朋友,点点关注不迷路

有人乐观,必有人悲观。锁的世界也不例外,乐观锁和悲观锁的悲欢各不相同。

摘要

  1. 什么是悲观锁
  2. 悲观锁的实现
  3. Monitor Object模式
  4. synchronized的实现原理
  5. 锁优化

1. 什么是悲观锁

悲观锁和乐观锁完全不同,悲观锁是实实在在对代码块进行加锁,被锁住的代码块,同一时刻只允许一个或几个线程同时进入,避免了多线程写坏共享数据问题。

2. 悲观锁的实现

Java中的悲观锁主要有以下几个实现:

  • synchronized
  • 基于AQS实现的各种Lock

3. Monitor Object模式

Monitor Object模式是为了解决线程安全问题,将多线程并发访问的对象定义为一个Monitor对象,每一个Monitor对象都会有一个Monitor锁,只要拿到该Monitor锁的线程才可以进入同步代码块或方法执行。此外同步方法或者代码块可以依据Monitor对象相关的Monitor Conditions来决定线程是否阻塞和执行。

Monitor Object模式的主要角色:

  • Monitor对象:对于每一个Java对象来说都是Monitor对象
  • 同步方法或代码块:这些方法会被Monitor对象的Monitor锁监视,在任何一个时间只能允许一个线程执行
  • Monitor锁:每一个Java对象都会有个Monitor锁
  • Monitor Conditions:同步方法或代码块通过Monitor锁和Monitor Conditions来决定线程是否需要阻塞或者执行(这里对应Java的Object.notify、Object.notifyAll以及Object.wait方法)

Java中的Monitor的实现如下,该实现位于JDK源码hotspot/src/share/vm/runtime/objectMonitor.hpp,如下:

代码语言:javascript
复制
ObjectMonitor() {
    _header       = NULL;
    _count        = 0; // 计数器
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL; // 持有锁的线程
    _WaitSet      = NULL; // 处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; // 处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

4. synchronized的实现原理

代码语言:javascript
复制
private final Object lock = new Object();

public int incrementAndGet(int a) {
    synchronized (lock) {
        return a + 1;
    }
}

熟悉Java的读者对上述代码必定不会陌生,当大家敲下这段代码的时候有思考过以下问题么?

  • synchronized同步代码块和同步方法有什么区别
  • 为什么只有一个线程可以进入临界资源
  • 当一个线程获取进入临界资源许可时,如何处理其他线程

synchronized的修饰方法和同步代码块本质上是没有什么区别的,都是对指定对象的Monitor锁的获取,只有获得对象的Monitor锁的线程才可以进入同步方法或者同步代码块执行,其他获取失败的线程会被阻塞放入Blocked队列

4.1 同步代码块和同步方法区别

synchronized的修饰方法在经过编译后,在方法的标志位上会有一个ACC_SYNCHRONIZED来标明该方法是一个同步方法。

synchronized修饰的方法调用步骤:

  1. 线程调用方法发现方法标志位有ACC_SYNCHRONIZED,因此去获取此实例对象的Monitor锁
  2. 如果Monitor锁此时被其他线程持有,则阻塞进入Blocked队列。
  3. 如果Monitor锁没有被其他线程持有,则获取成功,开始执行。
  4. 线程执行方法完成以后自动释放Monitor锁,允许其他线程获取。

synchronized同步代码块时在字节码层面是通过monitorenter和monitorexit指令来实现的,synchronized修饰方法时并不需要通过字节码指令。

synchronized同步代码块的调用步骤:

  1. 线程执行monitorenter指令去申请Monitor锁,Monitor锁中维护着一个计数器,默认值为0
  2. 如果此时计数器的值为0,表明当前线程可以获取该Monitor锁,当前线程获取锁后,计数器值+1,并且将Monitor中锁的线程修改为当前线程
  3. 如果获取失败,则进入Monitor锁中_EntryList(Blocked队列)
  4. 持有锁的线程执行完成以后,计数器会减1,并且将当前线程清空。

5. 锁优化

synchronized在JDK6以前是一个重量级锁,效率低下,因为Monitor锁是依赖于操作系统的Mutex Lock来实现的,操作系统的线程间的切换需要在用户态和内核态间互相转换,这种转换是比较耗时的。

在JDK6以后,当我们使用synchronized时,JVM会对锁进行优化,优化需要依赖Java对象头的结构,不了解Java对象的戳这里,下一篇我们会讲述JVM是如何进行锁优化的。

本期的Java悲观锁介绍到这,我是shysh95,我们下期再见!!!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员修炼笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档