共计 10479 个字符,预计需要花费 27 分钟才能阅读完成。
没什么太多说的,多线程与高并发,面试重点,咱间接进入正题,联结底层源码,咱们从源码看一下,多线程与高并发底层的知识点,这也是阿里 p8+ 的面试官倡议的学习到的级别
CAS
Compare And Swap (Compare And Exchange) / 自旋 / 自旋锁 / 无锁
因为常常配合循环操作,直到实现为止,所以泛指一类操作
cas(v, a, b),变量 v,期待值 a, 批改值 b
ABA 问题,你的女朋友在来到你的这段儿工夫经验了别的人,自旋就是你空转期待,始终等到她接收你为止
解决办法(版本号 AtomicStampedReference),根底类型简略值不须要版本号
Unsafe
AtomicInteger:
public final int incrementAndGet() {for (;;) {int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Unsafe:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
使用:
package com.mashibing.jol;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class T02_TestUnsafe {
int i = 0;
private static T02_TestUnsafe t = new T02_TestUnsafe();
public static void main(String[] args) throws Exception {//Unsafe unsafe = Unsafe.getUnsafe();
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Field f = T02_TestUnsafe.class.getDeclaredField("i");
long offset = unsafe.objectFieldOffset(f);
System.out.println(offset);
boolean success = unsafe.compareAndSwapInt(t, offset, 0, 1);
System.out.println(success);
System.out.println(t.i);
//unsafe.compareAndSwapInt()}
}
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 指令在执行前面指令的时候锁定一个北桥信号
(不采纳锁总线的形式)
工具:JOL = Java Object Layout
<dependencies>
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
</dependencies>
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 的横切面详解
- synchronized 原理
- 降级过程
- 汇编实现
- 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 位是偏差锁位 两位是一般锁位
- Object o = new Object() 锁 = 0 01 无锁态
- o.hashCode() 001 + hashcode00000001 10101101 00110100 00110110
01011001 00000000 00000000 00000000little endian big endian 00000000 00000000 00000000 01011001 00110110 00110100 10101101 00000000 - 默认 synchronized(o) 00 -> 轻量级锁 默认状况 偏差锁有个时延,默认是 4 秒 why? 因为 JVM 虚拟机本人有一些默认启动的线程,外面有好多 sync 代码,这些 sync 代码启动时就晓得必定会有竞争,如果应用偏差锁,就会造成偏差锁一直的进行锁撤销和锁降级的操作,效率较低。-XX:BiasedLockingStartupDelay=0
- 如果设定上述参数 new Object () – > 101 偏差锁 -> 线程 ID 为 0 -> Anonymous BiasedLock 关上偏差锁,new 进去的对象,默认就是一个可偏差匿名对象 101
- 如果有线程上锁 上偏差锁,指的就是,把 markword 的线程 ID 改为本人线程 ID 的过程 偏差锁不可重偏差 批量偏差 批量撤销
- 如果有线程竞争 撤销偏差锁,降级轻量级锁 线程在本人的线程栈生成 LockRecord,用 CAS 操作将 markword 设置为指向本人这个线程的 LR 的指针,设置成功者失去锁
- 如果竞争加剧 竞争加剧:有线程超过 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
public String test(String str){
int i = 0;
StringBuffer sb = new StringBuffer():
while(i < 100){sb.append(str);
i++;
}
return sb.toString():}
JVM 会检测到这样一连串的操作都对同一个对象加锁(while 循环内 100 次执行 append,没有锁粗化的就要进行 100 次加锁 / 解锁),此时 JVM 就会将加锁的范畴粗化到这一连串的操作的内部(比方 while 空幻体外),使得这一连串操作只须要加一次锁即可。
关注公众号:Java 架构师联盟,每日更新技术好文
局部材料曾经上传到我的 git 仓库中:有须要的能够下载
https://gitee.com/biwangsheng/mxq