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(); }