synchronized 原理分析

synchronized 原理分析

synchronized 是Java 提供的同步源语,它为 共享资源 提供了原子性 和 可见性保障,本文通过原子性 和 可见性 二个维度分析其实现原理

sync 原子性

通过 monitor 保证 原子性,具体表现为 monitorenter 和 monitorexit 或 ACC_SYNCHRONIZED 来实现加锁

加锁流程如下
sync.jpg

锁升级流程
  • new 对象时,判断 是否开启偏向锁

    • 开启偏向锁,构建匿名偏向锁(101)
    • 关闭偏向锁,构建无锁对象(001)
  • 无锁(001)遇到 线程加锁时,直接加自旋锁/轻量锁(00)
  • 偏向锁 遇到 一个线程加锁时,锁状态不变,保存线程ID
  • 偏向锁 遇到 多个线程交替加锁时,线程跑到安全点,撤消偏向锁,升级为自旋锁/轻量锁(00)
  • 自旋锁 是 每个线程通过CAS指令去更新对象头里面的markword,如果自旋失败次数、或自旋等待时间过长,锁膨胀成重量级锁(10)
  • 重量级锁 由 ObjectMonitor 实现,需要由用户态切换到内核态
  • 当竞争不激烈时,重量级锁 自动降级为轻量锁

monitorenter 源码分析

CASE(_monitorenter): {
    // 获取锁对象
    oop lockee = STACK_OBJECT(-1);
    // 在线程栈上找到一个空闲的BasicObjectLock对象
    BasicObjectLock* limit = istate->monitor_base();
    BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
    BasicObjectLock* entry = NULL;
    while (most_recent != limit ) {
        if (most_recent->obj() == NULL) entry = most_recent;
        else if (most_recent->obj() == lockee) break;
        most_recent++;
    }
    if (entry != NULL) {
        // 保存锁对象,表明当前BasicObjectLock持有锁对象lockee
        entry->set_obj(lockee); 
        int success = false;
        uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
        markOop mark = lockee->mark();   // 获取锁对象的头部标记信息
        // 获取没有hash值的标记位值,这里为0
        intptr_t hash = (intptr_t) markOopDesc::no_hash; 
        // 判断使用了偏向锁
        if (mark->has_bias_pattern()) {
            uintptr_t thread_ident;
            uintptr_t anticipated_bias_locking_value;
            thread_ident = (uintptr_t)istate->thread(); // 获取线程id
            anticipated_bias_locking_value =
              (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
              ~((uintptr_t) markOopDesc::age_mask_in_place);
            /* anticipated_bias_locking_value为0,表明还没有批量撤销偏向锁,且当前线程
              持有了偏向锁,直接退出 */
            if  (anticipated_bias_locking_value == 0) {
                // already biased towards this thread, nothing to do
                if (PrintBiasedLockingStatistics) {
                    (* BiasedLocking::biased_lock_entry_count_addr())++;
                }
                success = true;
            }
            else if ((anticipated_bias_locking_value & 
                markOopDesc::biased_lock_mask_in_place) != 0) {
                /* anticipated_bias_locking_value不为0,可能是批量撤销偏向锁,需要继续判断是否有
                 线程持有偏向锁,如果其他线程持有偏向锁,判定发生了冲突,就需要撤销偏向锁 */
                markOop header = lockee->klass()->prototype_header();
                if (hash != markOopDesc::no_hash) {
                    header = header->copy_set_hash(hash);
                }
                // CAS将对象头从mark替换为header撤销偏向锁
                if (lockee->cas_set_mark(header, mark) == mark) {
                    if (PrintBiasedLockingStatistics)
                        (*BiasedLocking::revoked_lock_entry_count_addr())++;
                }
            }
            else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
                /* 如果anticipated_bias_locking_value不为0,在批量撤销偏向锁时需要更改
                  epoch的值,这里如果epoch改变了,当前线程需要重偏向 */
                markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
                if (hash != markOopDesc::no_hash) {
                    new_header = new_header->copy_set_hash(hash);
                }
                // CAS重偏向
                if (lockee->cas_set_mark(new_header, mark) == mark) {
                    if (PrintBiasedLockingStatistics)
                        (* BiasedLocking::rebiased_lock_entry_count_addr())++;
                }
                else {
                    // CAS失败,发生了竞争,那么进入monitorenter
                    CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
                }
                success = true;
            }
            else {
                /* 以上条件均不满足,表明开启了偏向锁,此时偏向锁状态为匿名偏向,尝试CAS
                  将其偏向为当前线程*/
                markOop header = (markOop) ((uintptr_t) mark & 
                     ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
                      (uintptr_t)markOopDesc::age_mask_in_place |
                      epoch_mask_in_place));
                if (hash != markOopDesc::no_hash) {
                    header = header->copy_set_hash(hash);
                }
                markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
                // CAS重偏向
                if (lockee->cas_set_mark(new_header, header) == header) {
                    if (PrintBiasedLockingStatistics)
                        (* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
                }
                else {
                    // CAS失败,发生了竞争,那么进入monitorenter
                    CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry),
                         handle_exception);
                }
                success = true;
            }
        }
        // 没有获取到锁,那么进入传统的轻量级锁
        if (!success) {
            markOop displaced = lockee->mark()->set_unlocked();
            entry->lock()->set_displaced_header(displaced);
            bool call_vm = UseHeavyMonitors;  // 判断是否直接使用重量级锁
            /* 如果没有指定直接使用重量级锁,那么通过CAS操作尝试获取轻量级锁,即替换
              头部指针,指向entry */
            if (call_vm || lockee->cas_set_mark((markOop)entry, displaced) != displaced) {
                // 如果失败,可能是当前线程轻量级锁重入,那么判断是否是锁重入
                if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) 
                {
                    // 轻量级锁重入,不需要设置displaced_header信息
                    entry->lock()->set_displaced_header(NULL); 
                } else {
                    // 否则调用monitorenter
                    CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), 
                        handle_exception);
                }
            }
        }
        UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
    } else {
        // 如果未找到,设置more_monitors标志位,由解释器分配新的BasicObjectLock并重试
        istate->set_msg(more_monitors);
        UPDATE_PC_AND_RETURN(0);   // Re-execute
    }
}

sync 可见性

sync 通过 缓存一致性协议 保证可见性

MESI

M(modified):修改

E(exclusive):独占

S(shared):共享

I(invalid):无效

image.png

sync 和 Lock 的区别

使用

  • sync 自动加锁、解锁,Lock 需要手动加锁、解锁

功能

  • Lock 支持不同的Condition(不同的等待队列),指定唤醒
  • Lock 可以使用 tryLock 支持超时

    • sync锁 不支持超时
  • Lock 可以使用Lock.lockInterruptibly 响应中断

    • 没有获取到 sync锁 的线程处于 Blocked 状态不能响应interrupt中断
  • Lock 支持公平锁 和 非公平锁 ,sync 只支持非公平锁

原理

  • sync 底层由4种不同状态的锁升级实现, Lock 由 AQS(state + CLH)实现,属于乐观锁

    • 无锁、偏向锁、轻量锁都属于用户态
    • 轻量锁 由CAS实现,属于乐观锁
    • 重量级锁由 Monitor 实现,属于悲观锁
0 声望
0 粉丝
0 条评论
推荐阅读
AQS的源码逐行分析
AQS的源码分析NonfairSync 加锁源码分析 {代码...} {代码...} {代码...} {代码...} {代码...}

Echo阅读 334

Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀22阅读 55.1k评论 1

刨根问底 Redis, 面试过程真好使
充满寒气的互联网如何在面试中脱颖而出,平时积累很重要,八股文更不能少!下面带来的这篇 Redis 问答希望能够在你的 offer 上增添一把🔥。

菜农曰17阅读 946

封面图
PHP转Go实践:xjson解析神器「开源工具集」
我和劲仔都是PHP转Go,身边越来越多做PHP的朋友也逐渐在用Go进行重构,重构过程中,会发现php的json解析操作(系列化与反序列化)是真的香,弱类型语言的各种隐式类型转换,很大程度的减低了程序的复杂度。

王中阳Go10阅读 1.9k评论 2

封面图
万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide5阅读 789

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬8阅读 1.1k

与RabbitMQ有关的一些知识
工作中用过一段时间的Kafka,不过主要还是RabbitMQ用的多一些。今天主要来讲讲与RabbitMQ相关的一些知识。一些基本概念,以及实际使用场景及一些注意事项。

lpe2348阅读 1.9k

封面图
0 声望
0 粉丝
宣传栏