随笔
Java 的线程状态主要有五种,定义在 Thread 类里:
public enum State {
// 新建的还没有开始线程
NEW,
// 正在 JVM 中执行但是可能正在等待来自操作系统的其他资源,比如 cpu
RUNNABLE,
// 正在等待一个监视锁 来首次进入或(在调用 Object.wait 后)重新进入同步块
BLOCKED,
/**
* 等同于操作系统的挂起状态
*
* 在下列情况发生时,线程会进入此状态:
* 1. 无超时的 Object#wait()
* 2. 无超时的 Thread#join()
* 3. LockSupport#park()
*
* 如果要结束这个状态,必须由外部激活它
*/
WAITING,
/**
* 带有时间限制的 WAITING 状态,超过这个时间就自动结束这个状态,以下方法的调用会使一个线程进入此状态:
*
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
*/
TIMED_WAITING,
// 终止状态,线程已完成执行
TERMINATED;
}
可重入锁
如何锁
非公平锁
final void lock() {
// 如果原来没有线程持有此锁,尝试使用CAS获取它的锁,如果成功就可以直接获得锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 否则便是有线程持有该锁,就开始获取锁的漫漫长路
acquire(1);
}
public final void acquire(int arg) {
// 首先尝试获取锁,失败则从队列中获取锁直到成功
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 如果有中断发生,那就中断自己
selfInterrupt();
}
如何尝试获取锁?tryAcquire(arg) 会调用此方法:
// 不公平地获取锁
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前锁状态
int c = getState();
// 如果没有线程持有锁
if (c == 0) {
// 那么尝试使用CAS修改锁状态,如果成功则当前线程获得锁成功
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 否则如果当前线程就是当前持有锁的线程,直接修改锁状态
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 其余情况都算是尝试获取锁失败
return false;
}
简单来说就是,如果没有线程持有这个锁,那我就拿到锁了;如果当前拿着锁的线程就是我自己,那我又拿到锁了;其他情况都拿不到锁。锁可被多个线程持有,故名可重入。
// 从等待队列中获取锁,挂起式等待
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 这个对象不可达
p.next = null; // help GC
// 获取成功了
Failed = false;
// 返回中断标识
return interrupted;
}
// 如果当前节点应该睡觉,那就将当前线程挂起(挂起后这个循环将不再获得 cpu 资源,直到被唤起才会获得 cpu 资源),并且检查一下是否有中断发生了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 如果在当前线程被挂起之后,有当前线程的中断发生了
interrupted = true;
}
} finally {
// 如果最终获取锁失败了,那就不拿了!
if (Failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前一个节点处于什么状态
int ws = pred.waitStatus;
// 如果它的前一个节点处于将会释放锁的状态,那么它就可以放心睡觉了,因为它的前一个节点会叫醒它
if (ws == Node.SIGNAL)
return true;
// 如果它的前一个节点处于放弃锁的状态
if (ws > 0) {
// 那么在队列中一直往前找,直到找到不放弃锁的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 将那些放弃锁的节点都扔了
pred.next = node;
} else {
// 如果不是上面那些情况,也就是那些既没有放弃锁又不会叫醒我的节点,那就让它们会叫醒我
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 然后说,我还不可以睡觉!
return false;
}
private final boolean parkAndCheckInterrupt() {
// 令当前线程挂起
LockSupport.park(this);
// 返回是否有线程被中断了
return Thread.interrupted();
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。