Skip to content

Commit 5d48851

Browse files
committed
Update Java Notes
1 parent df4af1f commit 5d48851

File tree

1 file changed

+206
-23
lines changed

1 file changed

+206
-23
lines changed

Prog.md

Lines changed: 206 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ t.start();
622622
| public final void suspend() | **挂起(暂停)线程运行** |
623623
| public final void resume() | 恢复线程运行 |
624624

625-
所以 Java 中线程的状态是阻塞,很少使用挂起
625+
626626

627627

628628

@@ -9289,7 +9289,7 @@ ReentrantReadWriteLock 其**读锁是共享锁,写锁是独占锁**
92899289

92909290
* **重入时升级不支持**:持有读锁的情况下去获取写锁会导致获取写锁永久等待,需要先释放读,再去获得写
92919291

9292-
* **重入时降级支持**:持有写锁的情况下去获取读锁
9292+
* **重入时降级支持**:持有写锁的情况下去获取读锁,造成只有当前线程会持有读锁,因为写锁会互斥其他的锁
92939293

92949294
```java
92959295
w.lock();
@@ -9357,7 +9357,7 @@ public static void main(String[] args) {
93579357

93589358
缓存更新时,是先清缓存还是先更新数据库
93599359

9360-
* 先清缓存:可能造成刚清理缓存还没有更新数据库,线程直接查询了数据库更新缓存
9360+
* 先清缓存:可能造成刚清理缓存还没有更新数据库,线程直接查询了数据库更新过期数据到缓存
93619361

93629362
* 先更新据库:可能造成刚更新数据库,还没清空缓存就有线程从缓存拿到了旧数据
93639363

@@ -9375,14 +9375,110 @@ public static void main(String[] args) {
93759375

93769376
#### 实现原理
93779377

9378-
##### 加锁原理
9378+
##### 成员属性
93799379

93809380
读写锁用的是同一个 Sycn 同步器,因此等待队列、state 等也是同一个,原理与 ReentrantLock 加锁相比没有特殊之处,不同是**写锁状态占了 state 的低 16 位,而读锁使用的是 state 的高 16 位**
93819381

9382-
* t1 w.lock(写锁),成功上锁 state = 0_1
9382+
* 读写锁:
9383+
9384+
```java
9385+
private final ReentrantReadWriteLock.ReadLock readerLock;
9386+
private final ReentrantReadWriteLock.WriteLock writerLock;
9387+
```
9388+
9389+
* 构造方法:默认是非公平锁,可以指定参数创建公平锁
9390+
9391+
```java
9392+
public ReentrantReadWriteLock(boolean fair) {
9393+
// true 为公平锁
9394+
sync = fair ? new FairSync() : new NonfairSync();
9395+
// 这两个 lock 共享同一个 sync 实例,都是由 ReentrantReadWriteLock 的 sync 提供同步实现
9396+
readerLock = new ReadLock(this);
9397+
writerLock = new WriteLock(this);
9398+
}
9399+
```
9400+
9401+
Sync 类的属性:
9402+
9403+
* 统计变量:
9404+
9405+
```java
9406+
// 用来移位
9407+
static final int SHARED_SHIFT = 16;
9408+
// 高16位的1
9409+
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
9410+
// 65535,16个1,代表写锁的最大重入次数
9411+
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
9412+
// 低16位掩码:0b 1111 1111 1111 1111,用来获取写锁重入的次数
9413+
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
9414+
```
9415+
9416+
* 获取读写锁的次数:
9417+
9418+
```java
9419+
// 获取读写锁的读锁分配的总次数
9420+
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
9421+
// 写锁(独占)锁的重入次数
9422+
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
9423+
```
9424+
9425+
* 内部类:
9426+
9427+
```java
9428+
// 记录读锁线程自己的持有读锁的数量(重入次数),因为 state 高16位记录的是全局范围内所有的读线程获取读锁的总量
9429+
static final class HoldCounter {
9430+
int count = 0;
9431+
// Use id, not reference, to avoid garbage retention
9432+
final long tid = getThreadId(Thread.currentThread());
9433+
}
9434+
// 线程安全的存放线程各自的 HoldCounter 对象
9435+
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
9436+
public HoldCounter initialValue() {
9437+
return new HoldCounter();
9438+
}
9439+
}
9440+
```
9441+
9442+
* 内部类实例:
9443+
9444+
```java
9445+
// 当前线程持有的可重入读锁的数量,计数为 0 时删除
9446+
private transient ThreadLocalHoldCounter readHolds;
9447+
// 记录最后一个获取【读锁】线程的 HoldCounter 对象
9448+
private transient HoldCounter cachedHoldCounter;
9449+
```
9450+
9451+
* 首次获取锁:
9452+
9453+
```java
9454+
// 第一个获取读锁的线程
9455+
private transient Thread firstReader = null;
9456+
// 记录该线程持有的读锁次数(读锁重入次数)
9457+
private transient int firstReaderHoldCount;
9458+
```
9459+
9460+
* Sync 构造方法:
93839461

93849462
```java
9385-
//lock() -> sync.acquire(1);
9463+
Sync() {
9464+
readHolds = new ThreadLocalHoldCounter();
9465+
// 确保其他线程的数据可见性,state 是 volatile 修饰的变量,重写该值会将线程本地缓存数据【同步至主存】
9466+
setState(getState());
9467+
}
9468+
```
9469+
9470+
9471+
9472+
***
9473+
9474+
9475+
9476+
##### 加锁原理
9477+
9478+
* t1 线程:w.lock(**写锁**),成功上锁 state = 0_1
9479+
9480+
```java
9481+
// lock() -> sync.acquire(1);
93869482
public void lock() {
93879483
sync.acquire(1);
93889484
}
@@ -9399,20 +9495,22 @@ public static void main(String[] args) {
93999495
int c = getState();
94009496
// 获得低 16 位, 代表写锁的 state 计数
94019497
int w = exclusiveCount(c);
9498+
// 说明有读锁或者写锁
94029499
if (c != 0) {
9403-
// c != 0 and w == 0 表示 r != 0,有读锁,读锁不能升级,直接返回false
9500+
// c != 0 and w == 0 表示有读锁,【读锁不能升级】,直接返回 false
94049501
// w != 0 说明有写锁,写锁的拥有者不是自己,获取失败
94059502
if (w == 0 || current != getExclusiveOwnerThread())
94069503
return false;
9407-
// 锁重入计数超过低 16 位, 报异常
9504+
9505+
// 执行到这里只有一种情况:【写锁重入】,所以下面几行代码不存在并发
94089506
if (w + exclusiveCount(acquires) > MAX_COUNT)
94099507
throw new Error("Maximum lock count exceeded");
9410-
// 写锁重入, 获得锁成功
9508+
// 写锁重入, 获得锁成功,没有并发,所以不使用 CAS
94119509
setState(c + acquires);
94129510
return true;
94139511
}
94149512

9415-
// c == 0,没有任何锁,判断写锁是否该阻塞,是 false 就尝试获取锁,失败返回 false
9513+
// c == 0,说明没有任何锁,判断写锁是否该阻塞,是 false 就尝试获取锁,失败返回 false
94169514
if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
94179515
return false;
94189516
// 获得锁成功,设置锁的持有线程为当前线程
@@ -9429,11 +9527,11 @@ public static void main(String[] args) {
94299527
}
94309528
```
94319529

9432-
* t2 r.lock(读锁),进入 tryAcquireShared 流程,如果有写锁占据,那么 tryAcquireShared
9530+
* t2 r.lock(**读锁**),进入 tryAcquireShared 流程
94339531

94349532
* 返回 -1 表示失败
94359533
* 如果返回 0 表示成功
9436-
* 返回正数表示还有多少后继节点支持共享模式,读写锁返回1
9534+
* 返回正数表示还有多少后继节点支持共享模式,读写锁返回 1
94379535

94389536
```java
94399537
public void lock() {
@@ -9451,28 +9549,113 @@ public static void main(String[] args) {
94519549
protected final int tryAcquireShared(int unused) {
94529550
Thread current = Thread.currentThread();
94539551
int c = getState();
9454-
// 16 位, 代表写锁的 state
9455-
// 如果是其它线程持有写锁, 并且写锁的持有者不是当前线程,获取读锁失败,【写锁允许降级】
9552+
// exclusiveCount(c) 代表低 16 位, 写锁的 state,成立说明有线程持有写锁
9553+
// 写锁的持有者不是当前线程,则获取读锁失败,【写锁允许降级】
94569554
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
94579555
return -1;
94589556

9459-
// 高 16 位,代表读锁的 state
9557+
// 高 16 位,代表读锁的 state,共享锁分配出去的总次数
94609558
int r = sharedCount(c);
9461-
if (!readerShouldBlock() && // 读锁不该阻塞
9462-
r < MAX_COUNT && // 小于读锁计数
9463-
compareAndSetState(c, c + SHARED_UNIT)) {// 尝试增加计数成功
9464-
// ....
9559+
// 读锁是否应该阻塞
9560+
if (!readerShouldBlock() && r < MAX_COUNT &&
9561+
compareAndSetState(c, c + SHARED_UNIT)) { // 尝试增加读锁计数
9562+
// 加锁成功
9563+
// 加锁之前读锁为 0,说明当前线程是第一个读锁线程
9564+
if (r == 0) {
9565+
firstReader = current;
9566+
firstReaderHoldCount = 1;
9567+
// 第一个读锁线程是自己就发生了读锁重入
9568+
} else if (firstReader == current) {
9569+
firstReaderHoldCount++;
9570+
} else {
9571+
// cachedHoldCounter 设置为当前线程的 holdCounter 对象,即最后一个获取读锁的线程
9572+
HoldCounter rh = cachedHoldCounter;
9573+
// 说明还没设置 rh
9574+
if (rh == null || rh.tid != getThreadId(current))
9575+
// 获取当前线程的锁重入的对象,赋值给 cachedHoldCounter
9576+
cachedHoldCounter = rh = readHolds.get();
9577+
// 还没重入
9578+
else if (rh.count == 0)
9579+
readHolds.set(rh);
9580+
// 重入 + 1
9581+
rh.count++;
9582+
}
94659583
// 读锁加锁成功
94669584
return 1;
94679585
}
9468-
// 与 tryAcquireShared 功能类似, 但会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
9586+
// 逻辑到这 应该阻塞,或者 cas 加锁失败
9587+
// 会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
94699588
return fullTryAcquireShared(current);
94709589
}
9471-
// 非公平锁 readerShouldBlock 看 AQS 队列中第一个节点是否是写锁,是则阻塞,反之不阻塞
9590+
// 非公平锁 readerShouldBlock 偏向写锁一些,看 AQS 阻塞队列中第一个节点是否是写锁,是则阻塞,反之不阻塞
9591+
// 防止一直有读锁线程,导致写锁线程饥饿
94729592
// true 则该阻塞, false 则不阻塞
94739593
final boolean readerShouldBlock() {
94749594
return apparentlyFirstQueuedIsExclusive();
94759595
}
9596+
final boolean readerShouldBlock() {
9597+
return hasQueuedPredecessors();
9598+
}
9599+
```
9600+
9601+
```java
9602+
final int fullTryAcquireShared(Thread current) {
9603+
// 当前读锁线程持有的读锁次数对象
9604+
HoldCounter rh = null;
9605+
for (;;) {
9606+
int c = getState();
9607+
// 说明有线程持有写锁
9608+
if (exclusiveCount(c) != 0) {
9609+
// 写锁不是自己则获取锁失败
9610+
if (getExclusiveOwnerThread() != current)
9611+
return -1;
9612+
} else if (readerShouldBlock()) {
9613+
// 条件成立说明当前线程是 firstReader,当前锁是读忙碌状态,而且当前线程也是读锁重入
9614+
if (firstReader == current) {
9615+
// assert firstReaderHoldCount > 0;
9616+
} else {
9617+
if (rh == null) {
9618+
// 最后一个读锁的 HoldCounter
9619+
rh = cachedHoldCounter;
9620+
// 说明当前线程也不是最后一个读锁
9621+
if (rh == null || rh.tid != getThreadId(current)) {
9622+
// 获取当前线程的 HoldCounter
9623+
rh = readHolds.get();
9624+
// 条件成立说明 HoldCounter 对象是上一步代码新建的
9625+
// 当前线程不是锁重入,在 readerShouldBlock() 返回 true 时需要去排队
9626+
if (rh.count == 0)
9627+
// 防止内存泄漏
9628+
readHolds.remove();
9629+
}
9630+
}
9631+
if (rh.count == 0)
9632+
return -1;
9633+
}
9634+
}
9635+
// 越界判断
9636+
if (sharedCount(c) == MAX_COUNT)
9637+
throw new Error("Maximum lock count exceeded");
9638+
// 读锁加锁,条件内的逻辑与 tryAcquireShared 相同
9639+
if (compareAndSetState(c, c + SHARED_UNIT)) {
9640+
if (sharedCount(c) == 0) {
9641+
firstReader = current;
9642+
firstReaderHoldCount = 1;
9643+
} else if (firstReader == current) {
9644+
firstReaderHoldCount++;
9645+
} else {
9646+
if (rh == null)
9647+
rh = cachedHoldCounter;
9648+
if (rh == null || rh.tid != getThreadId(current))
9649+
rh = readHolds.get();
9650+
else if (rh.count == 0)
9651+
readHolds.set(rh);
9652+
rh.count++;
9653+
cachedHoldCounter = rh; // cache for release
9654+
}
9655+
return 1;
9656+
}
9657+
}
9658+
}
94769659
```
94779660

94789661
* 获取读锁失败,进入 sync.doAcquireShared(1) 流程开始阻塞,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态
@@ -9603,7 +9786,7 @@ public static void main(String[] args) {
96039786
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
96049787
continue;
96059788
}
9606-
// 条件不成立说明被唤醒的节点非常积极,直接将自己设置为了新的head
9789+
// 条件不成立说明被唤醒的节点非常积极,直接将自己设置为了新的 head
96079790
// 此时唤醒它的节点(前驱)执行 h == head 不成立,所以不会跳出循环,会继续唤醒新的 head 节点的后继节点
96089791
if (h == head)
96099792
break;
@@ -9632,7 +9815,7 @@ public static void main(String[] args) {
96329815

96339816
```java
96349817
protected final boolean tryReleaseShared(int unused) {
9635-
//
9818+
96369819
for (;;) {
96379820
int c = getState();
96389821
int nextc = c - SHARED_UNIT;

0 commit comments

Comments
 (0)