目录
线程并发带来的安全性问题
本地环境
前言
上一节我们学习了线程并发常见的安全性问题、锁的底层类型和对象结构的差异、锁升级相关知识。今天我们继续学习锁是如何升级的?
默认情况下,偏向锁的开启是有个延迟,默认是4秒。为什么这么设计呢?
再次运行下面的代码。
public class LockDemo7 {
static Object obj = new Object();
public static void main(String[] args) {
LockDemo7 demo = new LockDemo7(); // demo 和 obj 这2个对象,在内存中是如何存储和布局的。
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
System.out.println("-----------");
// 如果有其他线程进入到下面的同步块,则先自旋
// CAS 保证数据操作的原子性
synchronized(demo){// 轻量级锁
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
}
得到如下的对象布局,可以看到对象头中的的高位第一个字节最后三位数为[101],表示当前为偏向锁 状态。
这里的第一个对象和第二个对象的锁状态都是101,是因为偏向锁打开状态下,默认会有配置匿名的对象获得偏向锁。
com.example.juccode.LockDemo7 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000[101] 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 54 c4 00 f8 (01010100 11000100 00000000 11111000) (-134167468)
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.example.juccode.LockDemo7 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 18 80 cb (00000[101] 00011000 10000000 11001011) (-880797691)
4 4 (object header) b7 7f 00 00 (10110111 01111111 00000000 00000000) (32695)
8 4 (object header) 54 c4 00 f8 (01010100 11000100 00000000 11111000) (-134167468)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
为什么加锁和未加锁都是偏向锁呢?
main 方法启动的过程中,有很多内部匿名、默认线程执行,所以线程已经标记为偏向锁。
在竞争比较激烈的情况下,线程一直无法获得锁的时候,就会升级到重量级锁。仔细观察下面的案例,通过两个线程来模拟竞争的场景。
public class LockDemo8 {
public static void main(String[] args) {
LockDemo8 demo = new LockDemo8(); // demo 和 obj 这2个对象,在内存中是如何存储和布局的。
new Thread(()->{
synchronized (demo) {
System.out.println("Thread get Lock");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}).start();
synchronized(demo){// 如果当前锁是重量级锁,后面来的线程直接阻塞
System.out.println("Main Thread get Lock");
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
}
从结果可以看出,在竞争的情况下锁的标记为 [010] ,其中所标记 [10]表示重量级锁
com.example.juccode.LockDemo8 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 02 7e 01 a8 (00000[010] 01111110 00000001 10101000) (-1476297214)
4 4 (object header) ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
8 4 (object header) 40 70 06 00 (01000000 01110000 00000110 00000000) (421952)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Thread get Lock
com.example.juccode.LockDemo8 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 02 7e 01 a8 (00000[010] 01111110 00000001 10101000) (-1476297214)
4 4 (object header) ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
8 4 (object header) 40 70 06 00 (01000000 01110000 00000110 00000000) (421952)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
伪代码
// 自旋锁
for(;;){
// condition 自旋次数
if(cas){ // true ->
// 只有一个线程可以进来更改
break;
}
}
两个线程竞争锁,如何通过 CAS 实现轻量级锁呢?
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* <p>This operation has memory semantics of a {@code volatile} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@ForceInline
public final boolean compareAndSwapInt(Object o, long offset,
int expected,
int x) {
return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
}
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* <p>This operation has memory semantics of a {@code volatile} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
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
~/jdk/hotspot/hotspot-87ee5ee27509/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// 判断是不是多核,如果是多核的话,增加一个 Lock 指令
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;
}