java锁的系统调用 futex 与内存屏障
https://blog.csdn.net/xvktdmjg/article/details/114230993


1 aqs
cas-----
- 总线锁
LOCK#信号就是我们经常说到的 总线锁 ,处理器使用 LOCK# 信号达到锁定总线,来解决原子性问题,当一个处理器往总线上输出LOCK#信号时,其它处理器的请求将被阻塞,此时该处理器此时独占共享内存;总线锁这种做法锁定的范围太大了,导致CPU利用率急剧下降,因为使用LOCK#是把CPU和内存之间的通信锁住了,这使得锁定时期间,其它处理器不能操作其内存地址的数据 ,所以总线锁的 开销比较大 。
- 缓存锁
如果访问的内存区域已经缓存在处理器的缓存行中,P6系统和之后系列的处理器则不会声明LOCK#信号,它会对CPU的缓存中的缓存行进行锁定,在锁定期间,其它 CPU 不能同时缓存此数据,在修改之后,通过缓存一致性协议(在Intel CPU中,则体现为MESI协议)来保证修改的原子性,这个操作被称为 缓存锁
compareAndSetState -> Unsafe.compareAndSwapInt -> JNI(JVM) -> Atomic::cmpxchg -> lock cmpxchg(CPU指令)
上锁没有系统调用
其实park方法内部也用了CAS!重点关注一下此调用:pthread_cond_wait,就是调用此函数让线程阻塞的,这是POSIX线程(pthread)函数库中一个函数,感兴趣的可以看下它的源码:https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html
pthread_cond_wait内部调用了futex,而futex里面进行了系统调用sys_futex
在Linux中,为了挂起线程,使用 futex ,futex 即 Fast user space mutex
futex 通过用户态和内核配合,可以减小开销,并且线程灵活可控是否要进入睡眠等待还是spin等待。
futex 构成
futex 由一个32bit的futex word和一个系统调用sys_futex组成,futex word是进行互斥的变量,sys_futex 是通知内核对线程进行挂起和唤醒。
futex的使用模式
用户线程 通过 CAS 类原子指令尝试获取锁,如果成功,则获取锁成功。这种情况下无需调用系统调用,不需要进入内核。开销很小。
如果CAS失败,可以选择spin重试,也可以选择挂起自己等待唤醒。这里即调用系统调用,让内核操作挂起,为了保证锁原语,调用者将futex word的当前状态(锁定状态)作为参数传入内核,内核进行检查如果与futex word的当前一致,则挂起线程。否则返回失败。
为了唤醒等待线程,获取锁的线程在释放锁后,需要调用系统调用,来通知锁释放,内核会唤醒等待者进行竞争锁。
介绍说明futex不一定会进行系统调用,但是调用LockSupport.park()的时候线程确实阻塞了,没有在自旋(spin重试),因为自旋会消耗很多CPU资源,但是阻塞不会消耗,文末会证明LockSupport.park()确实进行了系统调用。
compareAndSetState -> Unsafe.compareAndSwapInt -> JNI(JVM) -> Atomic::cmpxchg -> lock cmpxchg(CPU指令)
LockSupport.park -> Unsafe.park -> JNI(JVM) -> Parker::park -> pthread_cond_wait -> futex -> sys_futex(系统调用)
LockSupport.unpark -> Unsafe.unpark -> JNI(JVM) -> Parker::unpark -> pthread_cond_signal -> futex -> sys_futex(系统调用)
if (time == 0) {
_cur_index = REL_INDEX; // arbitrary choice when not timed
// 进入等待并自动释放 mutex 锁,这里没有通过 while 包裹 wait 过程,所以会出现伪唤醒问题
status = pthread_cond_wait(&_cond[_cur_index], _mutex); }
else { _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
// 进入等待并自动释放 mutex 锁,这里没有通过 while 包裹 wait 过程,所以会出现伪唤醒问题
status = pthread_cond_timedwait(&_cond[_cur_index], _mutex, &absTime); }
2 syn

插播轻量级锁,对对象头markword cas写入currentthread
看Atomic::cmpxchg_ptr,看这名像啥?这不就是CAS么,所以又来到了熟悉的Atomic::cmpxchg,不明白的可以看上面ReentrantLock如何上锁
所谓上锁就是给ObjectMonitor._owner设置为指向获得锁的线程 == aqs == 轻量级锁
阻塞:
ObjectMonitor::enter -> os::PlatformEvent::park -> pthread_cond_timedwait -> futex -> sys_futex(系统调用)
唤醒:
ObjectMonitor::exit -> os::PlatformEvent::unpark -> pthread_cond_signal -> futex -> sys_futex(系统调用)
3
strace证明了pthread_cond_wait和pthread_cond_signal都调用了系统调用sys_futex
https://blog.51cto.com/yingnanxuezi/12351598
它允许线程在用户态进行大部分操作,只有在真正需要等待时,才会调用系统调用进入内核态。
用户态锁定:线程在用户态检查锁变量,如果发现锁未被持有,直接锁定并继续执行。
内核态等待:如果锁已被持有,线程调用 futex() 进入内核,内核将其挂起,直到有另一个线程解锁。
唤醒操作:当锁持有者释放锁时,调用 futex() 唤醒等待中的线程。
用户态和内核态切换较少的应用:由于 Futex 尽量避免系统调用,它非常适合那些频繁锁定但争用较少的场景。(有点CAS)
减少用户态与内核态的切换次数
Futex 锁与传统的 Mutex 锁有以下几个主要区别:
1. 性能差异
Futex:主要优势在于性能优化。Futex 允许线程在用户态进行大部分锁操作,仅在需要挂起时才切换到内核态。这减少了系统调用的次数,适合高并发场景。
Mutex:传统的 Mutex 锁通常在每次获取或释放锁时都需要进入内核态,这在多线程争用时可能导致性能下降。
2. 使用方式
Futex:直接与 Linux 内核的 futex() 系统调用交互,适用于低级别的同步需求,通常需要开发者自己实现。
Mutex:更高层的抽象,提供了较为简单的接口,通常由 pthread 库实现,适合一般的多线程应用。
3. 锁的实现
Futex:是用户态和内核态混合实现的同步原语,支持快速的用户态锁定,只有在等待时才切换到内核态。
Mutex:通常是完全内核态的实现,管理较复杂的线程调度和资源分配。
4. 特性支持
Futex:提供了高级的功能,如原子操作、可自定义的唤醒策略等。
Mutex:提供了基本的互斥锁功能,支持递归、优先级继承等特性,但没有像 Futex 那样的灵活性。
pthread 支持
是的,pthread 库支持 Futex 机制。实际上,pthread 中的 Mutex 实现通常会利用 Futex 来提高性能。在许多情况下,pthread_mutex 使用 Futex 作为底层实现,以便在高并发情况下实现更好的性能。
Futex 的系统调用如何与内存屏障(Memory Barrier)结合使用?
在修改锁状态之前使用内存屏障,确保所有的内存操作在锁状态改变前完成,避免因 CPU 缓存一致性导致的错误。
https://blog.csdn.net/weixin_37646636/article/details/131506996
什么是 load 操作, store 操作
load:从内存读到处理器的寄存器 ,也就是加载操作。
store:从处理器的寄存器写入到内存,就就是存储操作。
参考: load store 啥意思


StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他3个屏障的效果。现代的多处理器大多支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush)。缓存一致性 伪共享
LoadLoad:你先读我再读
LoadStore:你先读我再存,保证你读之前的,不被我存的影响
StoreStore:你先存我再存,决不能让你覆盖我
StoreLoad:你读完写完,我再读写,换句话说等您处理妥妥的我再上手,一点也不粘包
使用场景:
场景1、StoreStore:你先存我再存,决不能让你覆盖我
场景2、LoadLoad和LoadStore一般一起使用,表示我要读了以后,你才可以读写,我没读你连读都不行。
场景3、StoreLoad:表示我读完写完,你才可以读写,必须我做完了,你才可以开始
https://blog.csdn.net/2401_85767131/article/details/140074546

status = pthread_mutex_init (_mutex, NULL);
浙公网安备 33010602011771号