本文为读书笔记,书籍为Java并发编程的艺术
1.公平锁
公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。
使用公平锁时,加锁方法lock()调用轨迹如下。
1)ReentrantLock:lock()。
2)FairSync:lock()。
3)AbstractQueuedSynchronizer:acquire(int arg)。
4)ReentrantLock:tryAcquire(int acquires)。
在第4步真正开始加锁,下面是该方法的源代码。
//java/util/concurrent/locks/AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//java.util.concurrent.locks.ReentrantLock.FairSync
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// private volatile int state; 在 AbstractQueuedSynchronizer
int c = getState();//获取锁的开始,首先读volatile变量state
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
解锁:
1)ReentrantLock:unlock()。
2)AbstractQueuedSynchronizer:release(int arg)。
3)Sync:tryRelease(int releases)。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2.非公平锁
只要在自旋时设置同步状态成功就代表获取到了锁
加锁方法lock()调用轨迹如下。
1)ReentrantLock:lock()。
2)NonfairSync:lock()。
3)AbstractQueuedSynchronizer:compareAndSetState(int expect, int update)。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//获取不到锁
acquire(1);
}
//AbstractQueuedSynchronizer:compareAndSetState
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
解锁同公平锁
3. 获取不到锁的状态
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
m1();
}, "t1").start();
Thread.sleep(10);
//观察t2
new Thread(() -> {
m2();
}, "t2").start();
}
public static void m1() {
reentrantLock.lock();
try {
try {
//睡眠
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
reentrantLock.unlock();
}
}
public static void m2() {
System.out.println("尝试获取锁");
reentrantLock.lock();
try {
System.out.println("获取到锁");
} finally {
reentrantLock.unlock();
}
}
我们知道synchronized获取不到锁是阻塞状态的,这两个锁的状态不一样哦
至于原因就得看源码了
public final void acquire(int arg) {
// 再次尝试获取状态,如果还是获取不到就把 当前线程加入队列
//返回值是线程是否在自旋的过程中被挂起(park),如果被挂起了,就唤醒
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();// Thread.currentThread().interrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环
for (;;) {
// 得到前一个 节点
final Node p = node.predecessor();
//以下为自旋过程
// 查看是不是头结点 如果是 就尝试获取锁 获取成功
if (p == head && tryAcquire(arg)) {
//把当前节点设置为头结点
setHead(node);
p.next = null; // help GC
//是否失败获取 表明成功获取状态
failed = false;
//退出自旋,放回线程是否被挂起 (park)
return interrupted;
}
//以上的条件都没有满足,就把当前线程park
// 检查和更新未能获取的节点的状态。
//如果线程阻塞,返回true。这是主信号
//控制所有获取循环。需要pred == node.prev。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//结束的时候,如果还是获取不到状态,那就取消这个节点了
if (failed)
//取消正在进行的获取尝试。
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
// 挂起挂起
LockSupport.park(this);
//返回当前线程状态 true
return Thread.interrupted();
}
所以线程处于waitting状态,就是在自旋的过程中线程被阻塞了;
即 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())这一段关键代码
4. 可重入意义
请注意这个holdCount。
/**
*查询当前线程对该锁的持有数。
*
* 一个线程对每个没有锁的锁操作都持有一个锁,匹配解锁动作。
*
* <p>持有计数信息通常只用于测试和
*调试。例如,如果某一段代码应该
*不进入已经持有的锁,然后我们可以断言
* fact:
*
* <pre> {@code
* class X {
* ReentrantLock lock = new ReentrantLock();
* // ...
* public void m() {
* assert lock.getHoldCount() == 0;
* lock.lock();
* try {
* // ... method body
* } finally {
* lock.unlock();
* }
* }
* }}</pre>
*
* 返回当前线程对该锁的持有数,
*如果当前线程没有持有该锁,则为0
*/
public int getHoldCount() {
return sync.getHoldCount();
}
5.总结
- 公平锁和非公平锁释放时,最后都要写一个volatile变量state。
- 公平锁获取时,首先会去读volatile变量。
- 非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和volatile写的内存语义。
锁释放-获取的内存语义的实现至少有下面两种方式:
1)利用volatile变量的写-读所具有的内存语义。
2)利用CAS所附带的volatile读和volatile写的内存语义。