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);
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
// jdk8u - unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_ComapreAndSwapInt");
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
// 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) "cmpxchg1 %1.(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value
}
最终实现:
cmpxchg = cas修改变量值
lock cmpxchg 指令
硬件
lock指令在执行后面指令的时候锁定一个北桥信号
对象在内存中的存储布局
![](https://img.haomeiwen.com/i18102334/6f8dc09ecec31ee7.png)
JVM常用参数
-
jvm参数
64位系统指针默认是64bit
工具:JOL = Java Object Layout
Object o = new Object();
System.out.println(ClassLayout.parseIntance(o).toPrintable());
markword
对象头 8字节。标识着头对象的状态,被回收多少次
class pointer
-XX:+UseCompressedClassPointers 为4字节,不开启为8字节
T t = new T() t里面的class pointer指向t.class
.class文件被ClassLoader加载到内存空间的时候会为每个.class生成一个对象
通常来说一个.class文件只会被一个ClassLoader加载
instance data
成员变量
-XX:+UseCompressedOops 为4字节,不开启为8字节
Ordinary Object Pointer 引用类型
padding
8的倍数。数据是一块一块读取的,所以需要对齐。
![](https://img.haomeiwen.com/i18102334/49abed08c11148ab.png)
锁升级
https://www.jianshu.com/p/9998a9db17f7
锁状态
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
锁只能升级不能降级,jdk1.6之前都是重量级锁,为了降低获取锁的代价,才引入锁升级
偏向锁
大多数情况下,锁不存在多线程竞争,而是总由同一线程多次获取,为了使线程获得锁的代价更低而引入了偏向锁
偏向锁的获取
- 首先获取锁对象的markword,判断是否处于可偏向状态(biased_lock=1 & ThreadId = null)
- 当前可偏向状态,则通过CAS操作将当前线程ID写入到markword中
- 如果是已偏向状态,需要检测markword中存储的ThreadId是否为当前线程的,如果是不需要再次获得锁,如果不是说明当前锁存在竞争,需要升级到轻量级锁
偏向锁的撤销
线程是不会主动释放偏向锁的,只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会撤销偏向锁
- 偏向锁的撤销需要等待全局安全点,即在某个时间点上没有字节码正在执行是,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置为无锁状态,并撤销偏向锁(标志位0)或者轻量级锁(标志位00)的状态
轻量级锁
锁获取
- 线程在自己的栈帧中创建锁记录LockRecord,用于存储锁对象markword的拷贝
- 使用CAS操作尝试将锁对象的markword指向LockRecord,并将LockRecord的owner指向markword
未获取到锁的线程将通过自旋方式继续等待,如果自旋次数超过10次(-XX:PreBlockSpin控制) 或者自旋线程数超过CPU核数的一半(1.6后增加AdapativeSelfSpinning JVM自己控制)则轻量级锁升级为重量级锁
重量级锁
重量级锁通过对象内部的监视器monitor实现,monitor的本质是依赖于底层操作系统的mutex lock实现。操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本高。
锁粗化
加锁解锁需要消耗资源,如果存在一系列的连续加锁解锁操作,会导致不必要的性能损耗。
锁粗化就是将多个连续的加锁、解锁操作链接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作
for (int i = 0; i < size; i++) {
synchronized(lock) {}
}
// 锁粗化
synchronized(lock) {
for (int i = 0; i < size; i++) {}
}
锁消除
Java虚拟机在JIT编译时,通过对运行上下文的扫描,经过逃逸分析,去除不可能在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间
public synchronized void append(List<String> args) {
StringBuffer temp = new StringBuffer();
args.stream().forEarch(p -> temp.append(p));
return temp.toString();
}
ObjectMonitor
class ObjectMonitor {
protected:
ObjectWaiter * volatile _WaitSet; // LL of threads waiting on the monitor
ObjectWaiter * volatile _EntryList; // Threads blocked on entry or reentry
void * volatile _owner; // pointer to owning thread OR BaiscLock
volatile jlong _previous_owner_tid; // thread id of the previous owner of the monitor
ObjectWaiter * volatile _cxq; // LL of recently-arrived threads blocked on entry
// The list is actually composed of WaitNodes, acting as proxies for Threads
private:
volatile int _WaitSetLock; // protects Wait Queue - simple spinlock
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0;
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0;
_Responsible = NULL;
_succ = NULL;
_cxq = NULL;
FreeNext = NULL;
_EntryList = NULL;
_SpinFreq = 0;
_SpinClock = 0;
OwnerIsThread = 0;
_previous_owner_tid = 0;
}
}
synchronized修饰的方法(代码块):
- 当多个线程同时访问该方法,那么这些线程会先被放进_EntryList队列,此时线程处于Blocked状态
- 当一个线程获取到了实例对象的监控器(monitor)锁,那么就可以进入Running状态执行方法,此时ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象锁被一个线程获取
- 当Running状态的线程调用wait()方法,那么当前线程释放monitor对象进入Waiting状态,ObjectMonitor对象的_owner变为null,_count减1,同时线程进入_WaitSet队列,直到有线程调用notify()方法唤醒该线程,则该线程重新获取monitor对象进入_owner区
- 如果当前线程执行完毕,那么也释放monitor对象,进入Waiting状态,ObjectMonitor对象的_owner变为null,_count减1
synchronized实现
java源码层级:synchronized(o) / synchronized(.class)
字节码层级:monitorenter&monitorexit / ACC_SYNCHRONIZED
JVM层级:执行过程中自动锁升级
CPU汇编层级:lock cmpxchg
synchronized vs Lock(CAS)
在高争用 高耗时的环境下synchronized效率更高
在低争用 低耗时的环境下CAS效率更高
synchronized到重量级之后是等待队列,不消耗CPU
CAS等待期间消耗CPU
JMM(Java Memory Model)
原子性
指一个操作是不可中断的,即使是在多个线程一起执行的时候。一个操作一旦开始,就不会被其他线程干扰。例如CPU中的一些指令属于原子性的。i=1属于原子性,i++不属于原子性
有序性
指在单线程环境中,程序是按序依次执行的。
可见性
指当一个线程修改了某个共享变量的值,其他线程是否能够立即知道这个修改。
指令重排
指令重排是指在程序执行过程中,为了性能考虑,编译器和CPU可能会对指令重新排序,以达到最佳效果
编译器重排
// 优化前
int x = 1;
int y = 2;
int a1 = x * 1;
int b1 = y * 1;
int a2 = x * 2;
int b2 = y * 2;
// 优化后
int x = 1;
int y = 2;
int a1 = x * 1;
int a2 = x * 2;
int b1 = y * 1;
int b2 = y * 2;
CPU只读一次的x和y值,不需反复读取寄存器来交替x和y值
处理器重排
一条汇编指令的执行分为很多步骤:
- 取指IF
- 译码和取寄存器操作数ID
- 执行或者有效地址计算EX(ALU逻辑计算单元)
- 存储器访问MEM
- 写回WB(寄存器)
// 初始化
int a = 0;
int b = 0;
int x = 0;
int y = 0;
// 处理器A执行
a = 1; // A1
x = b; // A2
// 处理器B执行
b = 2; // B1
y = a; // B2
![](https://img.haomeiwen.com/i18102334/f5625832394bbb0e.png)
写缓存区没有及时刷新,使得处理器执行的读写操作与内存上顺序不一致。
网友评论