阿里P8面试官:硬件层级内存屏障如何帮助Java实现高并发?

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

jdk8u: unsafe.cpp:

cmpxchg = compare and exchange

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))

UnsafeWrapper(“Unsafe_CompareAndSwapInt”);

oop p = JNIHandles::resolve(obj);

jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);

return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

UNSAFE_END

jdk8u: atomic_linux_x86.inline.hpp

is_MP = Multi Processor

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {

int mp = os::is_MP();

asm volatile (LOCK_IF_MP(%4) “cmpxchgl %1,(%3)”

“=a” (exchange_value)

“r” (exchange_value), “a” (compare_value), “r” (dest), “r” (mp)

“cc”, “memory”);

return exchange_value;

}

jdk8u: os.hpp is_MP()

static inline bool is_MP() {

// During bootstrap if _processor_count is not yet initialized

// we claim to be MP as that is safest. If any platform has a

// stub generator that might be triggered in this phase and for

// which being declared MP when in fact not, is a problem - then

// the bootstrap routine for the stub generator needs to check

// the processor count directly and leave the bootstrap routine

// in place until called after initialization has ocurred.

return (_processor_count != 1) || AssumeMP;

}

jdk8u: atomic_linux_x86.inline.hpp

#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

最终实现:

cmpxchg = cas修改变量值

lock cmpxchg 指令

硬件:

lock指令在执行后面指令的时候锁定一个北桥信号

(不采用锁总线的方式)

markword

========

工具:JOL = Java Object Layout

===========================

org.openjdk.jol

jol-core

0.9

jdk8u: markOop.hpp

// Bit-format of an object header (most significant first, big endian layout below):

//

// 32 bits:

// --------

// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)

// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)

// size:32 ------------------------------------------>| (CMS free block)

// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

//

// 64 bits:

// --------

// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)

// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)

// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)

// size:64 ----------------------------------------------------->| (CMS free block)

//

// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)

// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)

// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)

// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

synchronized的横切面详解

==================

  1. synchronized原理

  2. 升级过程

  3. 汇编实现

  4. vs reentrantLock的区别

java源码层级


synchronized(o)

字节码层级


monitorenter moniterexit

JVM层级(Hotspot)


package com.mashibing.insidesync;

import org.openjdk.jol.info.ClassLayout;

public class T01_Sync1 {

public static void main(String[] args) {

Object o = new Object();

System.out.println(ClassLayout.parseInstance(o).toPrintable());

}

}

com.mashibing.insidesync.T01_Sync1$Lock object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)

4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)

8 4 (object header) 49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

com.mashibing.insidesync.T02_Sync2$Lock object internals:

OFFSET SIZE TYPE DESCRIPTION VALUE

0 4 (object header) 05 90 2e 1e (00000101 10010000 00101110 00011110) (506368005)

4 4 (object header) 1b 02 00 00 (00011011 00000010 00000000 00000000) (539)

8 4 (object header) 49 ce 00 20 (01001001 11001110 00000000 00100000) (536923721)

12 4 (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes tota

InterpreterRuntime:: monitorenter方法

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))

#ifdef ASSERT

thread->last_frame().interpreter_frame_verify_monitor(elem);

#endif

if (PrintBiasedLockingStatistics) {

Atomic::inc(BiasedLocking::slow_path_entry_count_addr());

}

Handle h_obj(thread, elem->obj());

assert(Universe::heap()->is_in_reserved_or_null(h_obj()),

“must be NULL or an object”);

if (UseBiasedLocking) {

// Retry fast entry if bias is revoked to avoid unnecessary inflation

ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);

} else {

ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);

}

assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),

“must be NULL or an object”);

#ifdef ASSERT

thread->last_frame().interpreter_frame_verify_monitor(elem);

#endif

IRT_END

synchronizer.cpp

revoke_and_rebias

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {

if (UseBiasedLocking) {

if (!SafepointSynchronize::is_at_safepoint()) {

BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);

if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {

return;

}

} else {

assert(!attempt_rebias, “can not rebias toward VM thread”);

BiasedLocking::revoke_at_safepoint(obj);

}

assert(!obj->mark()->has_bias_pattern(), “biases should be revoked by now”);

}

slow_enter (obj, lock, THREAD) ;

}

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {

markOop mark = obj->mark();

assert(!mark->has_bias_pattern(), “should not see bias pattern here”);

if (mark->is_neutral()) {

// Anticipate successful CAS – the ST of the displaced mark must

// be visible <= the ST performed by the CAS.

lock->set_displaced_header(mark);

if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {

TEVENT (slow_enter: release stacklock) ;

return ;

}

// Fall through to inflate() …

} else

if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {

assert(lock != mark->locker(), “must not re-lock the same lock”);

assert(lock != (BasicLock*)obj->mark(), “don’t relock with same BasicLock”);

lock->set_displaced_header(NULL);

return;

}

#if 0

// The following optimization isn’t particularly useful.

if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {

lock->set_displaced_header (NULL) ;

return ;

}

#endif

// The object header will never be displaced to this lock,

// so it does not matter what the value is, except that it

// must be non-zero to avoid looking like a re-entrant lock,

// and must not look locked either.

lock->set_displaced_header(markOopDesc::unused_mark());

ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

}

inflate方法:膨胀为重量级锁

锁升级过程

=====

JDK8 markword实现表:


无锁 - 偏向锁 - 轻量级锁 (自旋锁,自适应自旋)- 重量级锁

synchronized优化的过程和markword息息相关

用markword中最低的三位代表锁状态 其中1位是偏向锁位 两位是普通锁位

  1. Object o = new Object() 锁 = 0 01 无锁态

  2. o.hashCode() 001 + hashcode00000001 10101101 00110100 00110110 01011001 00000000 00000000 00000000 little endian big endian00000000 00000000 00000000 01011001 00110110 00110100 10101101 00000000

  3. 默认synchronized(o) 00 -> 轻量级锁 默认情况 偏向锁有个时延,默认是4秒 why? 因为JVM虚拟机自己有一些默认启动的线程,里面有好多sync代码,这些sync代码启动时就知道肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级的操作,效率较低。-XX:BiasedLockingStartupDelay=0

  4. 如果设定上述参数 new Object () - > 101 偏向锁 ->线程ID为0 -> Anonymous BiasedLock 打开偏向锁,new出来的对象,默认就是一个可偏向匿名对象101

  5. 如果有线程上锁 上偏向锁,指的就是,把markword的线程ID改为自己线程ID的过程 偏向锁不可重偏向 批量偏向 批量撤销

  6. 如果有线程竞争 撤销偏向锁,升级轻量级锁 线程在自己的线程栈生成LockRecord ,用CAS操作将markword设置为指向自己这个线程的LR的指针,设置成功者得到锁

  7. 如果竞争加剧 竞争加剧:有线程超过10次自旋, -XX:PreBlockSpin, 或者自旋线程数超过CPU核数的一半, 1.6之后,加入自适应自旋 Adapative Self Spinning , JVM自己控制 升级重量级锁:-> 向操作系统申请资源,linux mutex , CPU从3级-0级系统调用,线程挂起,进入等待队列,等待操作系统的调度,然后再映射回用户空间

(以上实验环境是JDK11,打开就是偏向锁,而JDK8默认对象头是无锁)

偏向锁默认是打开的,但是有一个时延,如果要观察到偏向锁,应该设定参数

没错,我就是厕所所长

加锁,指的是锁定对象

锁升级的过程

JDK较早的版本 OS的资源 互斥量 用户态 -> 内核态的转换 重量级 效率比较低

现代版本进行了优化

无锁 - 偏向锁 -轻量级锁(自旋锁)-重量级锁

偏向锁 - markword 上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,所以,偏向锁,偏向加锁的第一个线程 。hashCode备份在线程栈上 线程销毁,锁降级为无锁

有争用 - 锁升级为轻量级锁 - 每个线程有自己的LockRecord在自己的线程栈上,用CAS去争用markword的LR的指针,指针指向哪个线程的LR,哪个线程就拥有锁

自旋超过10次,升级为重量级锁 - 如果太多线程自旋 CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU)-XX:PreBlockSpin

自旋锁在 JDK1.4.2 中引入,使用 -XX:+UseSpinning 来开启。JDK 6 中变为默认开启,并且引入了自适应的自旋锁(适应性自旋锁)。

自适应自旋锁意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

偏向锁由于有锁撤销的过程revoke,会消耗系统资源,所以,在锁争用特别激烈的时候,用偏向锁未必效率高。还不如直接使用轻量级锁。

synchronized最底层实现


public class T {

static volatile int i = 0;

public static void n() { i++; }

public static synchronized void m() {}

publics static void main(String[] args) {

for(int j=0; j<1000_000; j++) {

m();

n();

}

}

}

java -XX:+UnlockDiagonositicVMOptions -XX:+PrintAssembly T

C1 Compile Level 1 (一级优化)

C2 Compile Level 2 (二级优化)

找到m() n()方法的汇编码,会看到 lock comxchg …指令

synchronized vs Lock (CAS)


在高争用 高耗时的环境下synchronized效率更高

在低争用 低耗时的环境下CAS效率更高

synchronized到重量级之后是等待队列(不消耗CPU)

CAS(等待期间消耗CPU)

一切以实测为准

锁消除 lock eliminate

==================

public void add(String str1,String str2){

StringBuffer sb = new StringBuffer();

sb.append(str1).append(str2);

}

我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add 方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。

锁粗化 lock coarsening

===================

总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。

Mybatis源码解析

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
gBuffer sb = new StringBuffer();

sb.append(str1).append(str2);

}

我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add 方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。

锁粗化 lock coarsening

===================

总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。

Mybatis源码解析

[外链图片转存中…(img-fMKP6sSl-1713370153590)]

[外链图片转存中…(img-I7wqvnkq-1713370153591)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-qTTRdomA-1713370153591)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值