CAS
Compare And Swap (Compare And Exchange) / 自旋 / 自旋锁 / 无锁
因为经常配合循环操作,直到完成为止,所以泛指一类操作
cas(v, a, b) ,变量v,期待值a, 修改值b
ABA问题,你的女朋友在离开你的这段儿时间经历了别的人,自旋就是你空转等待,一直等到她接纳你为止
解决办法(版本号 AtomicStampedReference),基础类型简单值不需要版本号
在多线程的状态下对一个值进行递增,原本的做法是 给其中这个线程加synchornized,然后其他线程就不能访问了,等值递增完,其他线程才可以访问,以此保持数据的一致性。
效率低
synchronized:同步锁。是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。
CAS:compare and swap。自旋/自旋锁/无锁
操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止
在JDK中的应用
JDK5之后添加AtomicInteger:原子类
它也可以实现递增,且不用加锁
a.incrementAndGet();
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
最后一步要读HotSpot VM源码
保证没有锁的状态下,多个线程对一个值的更新。
在这里插入图片描述
刚开始是A,后来改成B,再后来又改成了A。
如何解决ABA问题?
AtomicStampedReference
给值加一个版本号 1.0,2.0,只要任何一个线程对这个值进行更新,则版本号加1。再比较的时候除了比较值,还要比较版本号。
或者加一个布尔类型,改过则true。
CAS、Synchronized、volatile本质的实现都是一样的
JVM很多版本,默认大家说的都是Oracle的HotSpot的,因为sun公司被Oracle收购了
HotSpot VM
J9 VM
Zing VM
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指令在执行后面指令的时候锁定一个北桥信号
(不采用锁总线的方式)
markword
问题:
1.对象在内存中的布局
2.Object o = new Object()在内存中占了多少个字节?
markword:8个字节 classpointer :4个字节 object o是空的,所以0个i字节,12不能被8整除,所以补4个字节
所以是16个字节,这是在jvm默认开启压缩的情况下。
没有开启压缩的情况下,markword:8个字节;classpointer:8个字节,16个字节,已经对齐了。
所以开启压缩的情况下还是16个。
如果是一个person对象,里面有id和username两个成员变量。则person对象占多少个字节?
markword:8个字节
class pointer:默认开启压缩 4个字节
成员变量:int和String都是4个字节 一个8个字节
padding:补4个字节
一共24个字节
工具: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)
loss due to the next object alignment:由于下一个物体对齐而造成的损失
对象在内存中的存储布局:
普通对象:markword+class pointer+instance data+padding
markword+class pointer:对象头 12个字节
markword:关于锁的信息,关于synchronized的信息都存在这里 8个字节
class pointer:类型指针-这个对象到底属于哪个class的,指向到底属于哪个类 4个字节
instance data:实例数据-成员变量所占的地方。int 4个字节,long:8个字节
padding:对齐-当整个对象的字节数不能被8整除的时候,自动补齐被8整除(被8整除读的特别快)
-XX:InitialHeapSize=264377728 :起始堆大小
-XX:MaxHeapSize=4230043648:最大堆大小
-XX:+UseCompressedClassPointers:压缩指针 jvm64位的,指针就是64位的,8个字节。但是默认开启了UseCompressedClassPointers,会把8个字节压成4个字节。所以class pointers就占4个字节
-XX:+UseCompressedOops:oop ordinary object pointer 普通对象指针,String 压缩也是4个字节
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
这段话的意思是:执行这段代码的时候锁定这个对象(锁的是厕所门(对象)),锁的信息放在这个对象里面。锁定这段代码的说法是错误的,代码不能锁定
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
通过以上加锁前后的运行结果不同可知道,锁的信息是存在mark word里面。
synchronized的横切面详解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J1SOi2zG-1638933766951)(C:\Users\ke.c.liu\AppData\Roaming\Typora\typora-user-images\image-20211207113508360.png)]
cmpxchg
- synchronized原理
- 升级过程
- 汇编实现
- vs reentrantLock的区别
java源码层级
synchronized(o)
字节码层级
字节码也就是Java编译后的.class文件
IDEA里面的 view->ByteCode->
monitorenter moniterexit
MONITORENTER
L0
LINENUMBER 11 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
INVOKESTATIC org/openjdk/jol/info/ClassLayout.parseInstance (Ljava/lang/Object;)Lorg/openjdk/jol/info/ClassLayout;
INVOKEVIRTUAL org/openjdk/jol/info/ClassLayout.toPrintable ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L8
LINENUMBER 12 L8
ALOAD 2
MONITOREXIT
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实现表:
markword 8个字节,64位。
下面是锁定升级状态的信息存在markword中,不同状态的分布情况
前面无锁和偏向锁最后两位都是 0 1.
怎么在markword中区分各种锁:
先读后两位,如果都是0,1,再读第三位进行判断
如果不是0,1,直接就区分了。
无锁状态:
分代年龄:一个对象被垃圾回收器回收之后,它的年龄会加1
所以说markword里面记录了除了锁的信息,还有GC的信息
无锁状态 4bit,最大是1111,是15,所以最大年龄是15
hashcode调用的话占31位,不调用则为空。
可以加一个o.hashCode,再查看打印结果肯定是有变化的。
Object o = new Object();
o.hashCode();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
hashCode可以重复
偏向锁:
偏向于第一个刚刚进来的线程(在厕所门上贴个纸,xx专用即可,也就是线程ID),下次再用的时候,直接上这个厕所。
轻量级锁:
只要有竞争的 时候,自动升级为轻量级锁
抢坑
首先撤销偏向锁(撕掉厕所门上贴的纸)
然后 两个线程中的栈生成LR(Lock Record)的指针,看谁能指向厕所门。 LR里面记录着Hashcode
使用自旋的方式(CAS)来抢,谁修改成功,就是谁的。
重量级锁:
但是自旋锁太消耗CPU了(不是在队列里面),所以会进行下一步的操作,升级为重量级锁。
升级条件:自旋超过10次,或者超过CPU内核的一半了
轻量级锁是执行在用户态,根本不需要跟内核打交道
每个重量级锁下面都有一个队列,队列中没有轮到,则不消耗cpu,不会自旋,直接冷冻。属于wait状态
队列无序,synchronized默认是非公平锁。
为什么重量级锁消耗CPU比较大?
因为你从操作系统当中去申请,你惊动了老大。
从内核态转为用户态,从用户态向内核态申请,两种状态的切换。
new-偏向锁 - 轻量级锁 (无锁,自旋锁(CAS),自适应自旋)- 重量级锁
synchronized优化的过程和markword息息相关
用markword中最低的三位代表锁状态 其中1位是偏向锁位 两位是普通锁位
-
Object o = new Object()
锁 = 0 01 无锁态 -
o.hashCode()
001 + hashcode00000001 10101101 00110100 00110110 01011001 00000000 00000000 00000000
little 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默认对象头是无锁)
偏向锁默认是打开的,但是有一个时延,如果要观察到偏向锁,应该设定参数
没错,我就是厕所所长
加锁,指的是锁定对象
锁升级的过程
大多数app跑在用户态上
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 cmpxchg …指令
JIT:just in time complier :及时编译器
synchronized vs Lock (CAS)
在高争用 高耗时的环境下synchronized效率更高
在低争用 低耗时的环境下CAS效率更高
synchronized到重量级之后是等待队列(不消耗CPU)
CAS(等待期间消耗CPU)
一切以实测为准
锁消除 lock eliminate
就是sb这个对象,不断的去append,按理说要不断的去锁加锁,解锁加锁。但sb这个对象如果只在add这个方法中使用,不可能被其他线程使用,则JDK检测到之后,会把sb的锁消除。
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 虚幻体外),使得这一连串操作只需要加一次锁即可。
锁降级(不重要)
https://www.zhihu.com/question/63859501
“锁降级在某些特定的情况下会发生,这种特定情况下就是GC的时候,这把锁已经不被任何其他线程锁定了,所以这把锁会降级,但是都开始GC了,没有任何其他锁去访问了,所以降级没有任何意义了”
其实,只被VMThread访问,降级也就没啥意义了。所以可以简单认为锁降级不存在!
超线程
为什么要把硬盘的数据扔到内存里面才叫缓存?
因为硬盘太慢了
Cpu速度是内存速度的100倍,
Cpu速度是硬盘速度的100w倍,
CPU最核心的3个单元
ALU:逻辑和运算单元
Registers:寄存器
PC:指令寄存器
多核CPU->多个CPU
多核封装在一个CPU里面,多核共享L3
多个CPU就有多个L3,多个L3共享主内存,叫做主存。
这就是缓存的层次结构。
一个线程在cpu内部执行,这个线程执行到了什么位置,这个就是PC(指令寄存器)记录的。
这个线程的数据,寄存器记录的。
这个线程做计算,ALU来做
线程切换(context change)
A线程不用了,数据先存起来,B线程再去存到CPU的Registers和PC里面。
B线程不用了,数据存起来,A之前存起来的数据直接拿来用。
所以线程是CPU执行的基本单位,进程是CPU分配资源的基本单位。
一个ALU + (两组Registers + PC)所谓的4核8线程就是这个概念。
一个核里面的两个线程互相切换。
参考资料
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
volatile的用途
1.线程可见性
线程跑在CPU上,线程可见性也就是CPU可见性
package com.mashibing.testvolatile;
public class T01_ThreadVisibility {
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(()-> {
while (flag) {
//do sth
}
System.out.println("end");
}, "server").start();
Thread.sleep(1000);
flag = false;
}
}
为什么要把硬盘的数据扔到内存里面才叫缓存?
因为硬盘太慢了
Cpu速度是内存速度的100倍,
Cpu速度是硬盘速度的100w倍,
CPU最核心的3个单元
ALU:逻辑和运算单元
Registers:寄存器
PC:指令寄存器
多核CPU->多个CPU
多核封装在一个CPU里面,多核共享L3
多个CPU就有多个L3,多个L3共享主内存,叫做主存。
这就是缓存的层次结构。
线程切换(context change)
A线程不用了,数据先存起来,B线程再去存到CPU的Registers和PC里面。
B线程不用了,数据存起来,A之前存起来的数据直接拿来用。
所以线程是CPU执行的基本单位,进程是CPU分配资源的基本单位。
2.防止指令重排序
cpu乱序执行
问题:DCL单例需不需要加volatile?
CPU的基础知识
每个缓存里面都是由缓存行组成的,缓存系统中是以 缓存行(cache line) 为单位存储的。. 缓存行是2的整数幂个连续字节,一般为32-256个字节。. 最常见的缓存行大小是64个字节 。. 当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享 。. 缓存行上的写竞争是运行在SMP系统中并行线程实现可伸缩性最重要的限制因素。. 有人将伪共享描述成无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。.
-
缓存行对齐
缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高
Disruptorpackage com.mashibing.juc.c_028_FalseSharing; public class T02_CacheLinePadding { private static class Padding { public volatile long p1, p2, p3, p4, p5, p6, p7; // } private static class T extends Padding { public volatile long x = 0L; } public static T[] arr = new T[2]; static { arr[0] = new T(); arr[1] = new T(); } public static void main(String[] args) throws Exception { Thread t1 = new Thread(()->{ for (long i = 0; i < 1000_0000L; i++) { arr[0].x = i; } }); Thread t2 = new Thread(()->{ for (long i = 0; i < 1000_0000L; i++) { arr[1].x = i; } }); final long start = System.nanoTime(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println((System.nanoTime() - start)/100_0000); } }
底层如何实现数据一致性
1.MESI:Modified+Exclusive(独占)+Shared(共享)+Invalid 缓存一致性协议:英特尔cpu用的协议其他的不一定。
2.数据比较大,超过了整个缓存行的大小。锁总线。但效率没有MESI高
系统底层如何保证有序性
1.sfence mfence Ifence等系统原语
2.锁总线
-
伪共享
-
合并写
CPU内部的4个字节的Bufferpackage com.mashibing.juc.c_029_WriteCombining; public final class WriteCombining { private static final int ITERATIONS = Integer.MAX_VALUE; private static final int ITEMS = 1 << 24; private static final int MASK = ITEMS - 1; private static final byte[] arrayA = new byte[ITEMS]; private static final byte[] arrayB = new byte[ITEMS]; private static final byte[] arrayC = new byte[ITEMS]; private static final byte[] arrayD = new byte[ITEMS]; private static final byte[] arrayE = new byte[ITEMS]; private static final byte[] arrayF = new byte[ITEMS]; public static void main(final String[] args) { for (int i = 1; i <= 3; i++) { System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne()); System.out.println(i + " SplitLoop duration (ns) = " + runCaseTwo()); } } public static long runCaseOne() { long start = System.nanoTime(); int i = ITERATIONS; while (--i != 0) { int slot = i & MASK; byte b = (byte) i; arrayA[slot] = b; arrayB[slot] = b; arrayC[slot] = b; arrayD[slot] = b; arrayE[slot] = b; arrayF[slot] = b; } return System.nanoTime() - start; } public static long runCaseTwo() { long start = System.nanoTime(); int i = ITERATIONS; while (--i != 0) { int slot = i & MASK; byte b = (byte) i; arrayA[slot] = b; arrayB[slot] = b; arrayC[slot] = b; } i = ITERATIONS; while (--i != 0) { int slot = i & MASK; byte b = (byte) i; arrayD[slot] = b; arrayE[slot] = b; arrayF[slot] = b; } return System.nanoTime() - start; } }
-
指令重排序
package com.mashibing.jvm.c3_jmm; public class T04_Disorder { private static int x = 0, y = 0; private static int a = 0, b =0; public static void main(String[] args) throws InterruptedException { int i = 0; for(;;) { i++; x = 0; y = 0; a = 0; b = 0; Thread one = new Thread(new Runnable() { public void run() { //由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间. //shortWait(100000); a = 1; x = b; } }); Thread other = new Thread(new Runnable() { public void run() { b = 1; y = a; } }); one.start();other.start(); one.join();other.join(); String result = "第" + i + "次 (" + x + "," + y + ")"; if(x == 0 && y == 0) { System.err.println(result); break; } else { //System.out.println(result); } } } public static void shortWait(long interval){ long start = System.nanoTime(); long end; do{ end = System.nanoTime(); }while(start + interval >= end); } }
volatile如何解决指令重排序
1: volatile i
2: ACC_VOLATILE
3: JVM的内存屏障
4:hotspot实现
bytecodeinterpreter.cpp
int field_offset = cache->f2_as_index();
if (cache->is_volatile()) {
if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
OrderAccess::fence();
}
orderaccess_linux_x86.inline.hpp
inline void OrderAccess::fence() {
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}
&& y == 0) {
System.err.println(result);
break;
} else {
//System.out.println(result);
}
}
}
public static void shortWait(long interval){
long start = System.nanoTime();
long end;
do{
end = System.nanoTime();
}while(start + interval >= end);
}
}
### volatile如何解决指令重排序
1: volatile i
2: ACC_VOLATILE
3: JVM的内存屏障
4:hotspot实现
bytecodeinterpreter.cpp
```c++
int field_offset = cache->f2_as_index();
if (cache->is_volatile()) {
if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
OrderAccess::fence();
}
orderaccess_linux_x86.inline.hpp
inline void OrderAccess::fence() {
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}