JUC总结
# JUC 总结

# CompletableFuture
# “锁” 事儿
悲观锁
乐观锁
自旋锁
可重入锁(递归锁)
写锁(独占锁)/ 读锁(共享锁)
公平锁 / 非公平锁
死锁
偏向锁
轻量锁
重量锁
邮戳(票据)锁
# JMM
# synchronized 及升级优化
# 锁的到底是什么


- 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
- 作用于代码块,对括号里配置的对象加锁。
- 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
# 无锁→偏向锁→轻量锁→重量锁
# Java 对象内存布局和对象头
# 64 位图

# CAS
# CAS 的底层原理
比较并交换
//unsafe.cpp
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
2
3
4
5
6
7
JDK 提供的 CAS 机制,在汇编层级,会禁止变量两侧的指令优化,然后使用 cmpxchg 指令比较并更新变量值 (原子性)
# CAS 问题
# ABA 问题
问题: 线程 X 准备将变量的值从 A 改为 B,然而这期间线程 Y 将变量的值从 A 改为 C,然后再改为 A;最后线程 X 检测变量值是 A,并置换为 B。
但实际上,A 已经不再是原来的 A 了解决方法,是把变量定为唯一类型。值可以加上版本号,或者时间戳。
解决: 如加上版本号,线程 Y 的修改变为 A1->B2->A3,此时线程 X 再更新则可以判断出 A1 不等于 A3
# volatile
# 特性
# 内存屏障
# LockSupport
# 是什么
LockSupport 是基于 Unsafe 类,由 JDK 提供的线程操作工具类,主要作用就是挂起线程,唤醒线程。
LockSupport.park
LockSupport.unpark
# LockSupport.park 和 Object.wait 区别
线程在 Object.wait 之后必须等到 Object.notify 才能唤醒
LockSupport 可以先 unpark 线程,等线程执行 LockSupport.park 是不会挂起的,可以继续执行
# AbstractQueuedSynchronizer
# 是什么
volatile+cas 机制实现的锁模板,保证了代码的同步性和可见性,而 AQS 封装了线程阻塞等待挂起,解锁唤醒其他线程的逻辑。AQS 子类只需根据状态变量,判断是否可获取锁,是否释放锁,使用 LockSupport 挂起、唤醒线程即可
//AbstractQueuedSynchronizer.java
public class AbstractQueuedSynchronizer{
//线程节点
static final class Node {
volatile Node prev;
volatile Node next;
volatile Thread thread;
...
}
//head 等待队列头尾节点
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state; // The synchronization state. 同步状态
...
//提供CAS操作,状态具体的修改由子类实现
protected final boolean compareAndSetState(int expect, int update) {
return STATE.compareAndSet(this, expect, update);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 出队入队 Node

AQS 内部维护一个同步队列,元素就是包装了线程的 Node。
同步队列中首节点是获取到锁的节点,它在释放锁的时会唤醒后继节点,后继节点获取到锁的时候,会把自己设为首节点。 线程会先尝试获取锁,失败则封装成 Node,CAS 加入同步队列的尾部。在加入同步队列的尾部时,会判断前驱节点是否是 head 结点,并尝试加锁 (可能前驱节点刚好释放锁),否则线程进入阻塞等待。
# ThreadLocal
当使用 ThreadLocal 声明变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本, 每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本