synchronized保证被修饰的方法或代码块操作的原子性、可见性和有序性。synchronized可重入锁、不可中断,适合线程竞争不激烈。
在使用Sychronized关键字时需要把握如下注意点:
包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)
代码块形式:
手动指定锁定对象,也可是是this,也可以是自定义的锁
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
@Override
public void run() {
// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
synchronized (this) {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
// 创建2把锁
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run() {
// 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
synchronized (block1) {
System.out.println("block1锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");
}
synchronized (block2) {
System.out.println("block2锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
输出结果:
block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0 // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
block1锁,我是线程Thread-1
block2锁,Thread-0结束
block1锁,Thread-1结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束
方法锁形式:
synchronized修饰普通方法,锁对象默认为this
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
指synchronize修饰静态的方法或指定锁对象为Class对象
synchronize修饰静态方法
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在普通方法上,默认的锁就是this,当前实例
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
// t1和t2对应的this是两个不同的实例,所以代码不会串行
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
public static synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
synchronized指定锁对象为Class对象
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
@Override
public void run() {
// 所有线程需要的锁都是同一把
synchronized(SynchronizedObjectLock.class){
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
输出结果:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
深入JVM看字节码,创建如下的代码:
public class SynchronizedDemo2 {
Object object = new Object();
public void method1() {
synchronized (object) {
}
}
}
使用javac命令进行编译生成.class文件
>javac SynchronizedDemo2.java
使用javap命令反编译查看.class文件的信息
>javap -verbose SynchronizedDemo2.class
关注红色方框里的monitorenter和monitorexit即可。
Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每一个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下3中情况之一:
monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。
下图表现了对象,对象监视器,同步队列以及执行线程状态之间的关系:
该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。
Synchronized的happens-before规则,即监视器锁规则:对同一个监视器的解锁,happens-before于对该监视器的加锁。继续来看代码:
public class MonitorDemo {
private int a = 0;
public synchronized void writer() { // 1
a++; // 2
} // 3
public synchronized void reader() { // 4
int i = a; // 5
} // 6
}
该代码的happens-before关系如图所示:
在图中每一个箭头连接的两个节点就代表之间的happens-before关系,黑色的是通过程序顺序规则推导出来,红色的为监视器锁规则推导而出:线程A释放锁happens-before线程B加锁,蓝色的则是通过程序顺序规则和监视器锁规则推测出来happens-befor关系,通过传递性规则进一步推导的happens-before关系。现在我们来重点关注2 happens-before 5,通过这个关系我们可以得出什么?
根据happens-before的定义中的一条:如果A happens-before B,则A的执行结果对B可见,并且A的执行顺序先于B。线程A先对共享变量A进行加一,由2 happens-before 5关系可知线程A的执行结果对线程B可见即线程B所读取到的a的值为1。
简单来说在JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的,但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行,这种切换的代价是非常昂贵的;然而在现实中的大部分情况下,同步方法是运行在单线程环境(无锁竞争环境)如果每次都调用Mutex Lock那么将严重的影响程序的性能。不过在jdk1.6中对锁的实现引入了大量的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销。
锁的优缺点对比
锁 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比仅存在纳秒级的差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步快的场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了响应速度 | 如线程成始终得不到锁竞争的线程,使用自旋会消耗CPU性能 | 追求响应时间,同步快执行速度非常快 |
重量级锁 | 线程竞争不适用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 | 追求吞吐量,同步快执行速度较长 |
ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字相比,它更灵活、更强大,增加了轮询、超时、中断等高级功能。ReentrantLock,顾名思义,它是支持可重入锁的锁,是一种递归无阻塞的同步机制。除此之外,该锁还支持获取锁时的公平和非公平选择。
ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。
public class ReentrantLock implements Lock, java.io.Serializable
ReentrantLock类的sync非常重要,对ReentrantLock类的操作大部分都直接转化为对Sync和AbstractQueuedSynchronizer类的操作。
private final Sync sync;
默认是采用的非公平策略获取锁
public ReentrantLock() {
// 默认非公平策略
sync = new NonfairSync();
}
可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
内部类
ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。
ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。
Sync类的源码如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
// 序列号
private static final long serialVersionUID = -5179523762034025860L;
// 获取锁
abstract void lock();
// 非公平方式获取
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 表示没有线程正在竞争该锁
if (compareAndSetState(0, acquires)) { // 比较并设置状态为 acquires 成功,状态0表示锁没有被占用
// 设置当前线程独占
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;
}
// 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
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;
}
// 判断资源是否被当前线程占有
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 新生一个条件
final ConditionObject newCondition() {
return new ConditionObject();
}
// 返回资源的占用线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 返回状态
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 资源是否被占用
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
// 自定义反序列化逻辑
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法,源码如下:
// 非公平锁
static final class NonfairSync extends Sync {
// 版本号
private static final long serialVersionUID = 7316153563782823691L;
// 获得锁
final void lock() {
if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
// 把当前线程设置独占了锁
setExclusiveOwnerThread(Thread.currentThread());
else // 锁已经被占用,或者set失败
// 以独占模式获取对象,忽略中断
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
说明: 从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。
// 公平锁
static final class FairSync extends Sync {
// 版本序列化
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 以独占模式获取对象,忽略中断
acquire(1);
}
// 尝试公平获取锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 状态为0
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
// 设置当前线程独占
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 如果占用锁的线程为当前线程。
// 下一个状态
int nextc = c + acquires;
if (nextc < 0) // 超过了int的表示范围
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
return true;
}
return false;
}
}
说明: 跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。
说明: 可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。
ReentrantLock lock=new ReentrantLock();
lock.lock();
1.lock()
上面代码是我们常用的获取锁的方式:调用ReentrantLock 的lock()方法,默认情况下ReentrantLock是一个非公平锁,类名NonfairSync,属于ReentrantLock的内部类,我们来看源码:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
这里的lock方法先去通过CAS操作将state的值从0变为1,如果成功,就设置ExclusiveOwnerThread的值为当前线程,ReentrantLock用exclusiveOwnerThread表示“持有锁的线程”。如果设置失败,说明state>0已经有线程持有了锁,此时执行acquire(1)再请求一次锁。
2.acquire()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire方法步骤大概是请求独占锁,忽略所有中断,至少执行一次tryAcquire,如果成功就返回,否则线程进入阻塞–唤醒两种状态切换中,直到tryAcquire成功。
我们对里面的tryAcquire(),、addWaiter()、acquireQueued()挨个分析。
在这个方法里先执行tryAcquire(arg):
3.tryAcquire()
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (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;
}
先判断state是否为0,如果为0就执行上面提到的lock方法的前半部分,通过CAS操作将state的值从0变为1,否则判断当前线程是否为exclusiveOwnerThread,然后把state++,也就是重入锁的体现,我们注意前半部分是通过CAS来保证同步,后半部分并没有同步的体现,原因是:
后半部分是线程重入,再次获得锁时才触发的操作,此时当前线程拥有锁,所以对ReentrantLock的属性操作是无需加锁的。
如果tryAcquire()获取失败,则要执行addWaiter()向等待队列中添加一个独占模式的节点
4.addWaiter()
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
这个方法的注释:创建一个入队node为当前线程,Node.EXCLUSIVE 是独占锁,Node.SHARED 是共享锁。
先找到等待队列的tail节点pred,如果pred!=null,就把当前线程添加到pred后面进入等待队列,如果不存在tail节点执行enq()
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
这里进行了循环,如果此时存在了tail就执行同上一步骤的添加队尾操作,如果依然不存在,就把当前线程作为head结点。
插入节点后,调用acquireQueued()进行阻塞
5.acquireQueued()
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;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
先获取当前节点的前一节点p,如果p是head的话就再进行一次tryAcquire(arg)操作,如果成功就返回,否则就执行shouldParkAfterFailedAcquire、parkAndCheckInterrupt来达到阻塞效果;
6.shouldParkAfterFailedAcquire()
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;
}
addWaiter()构造的新节点,waitStatus的默认值是0。此时,进入最后一个if判断,CAS设置pred.waitStatus为SIGNAL==-1。最后返回false。
回到第五步acquireQueued()中后,由于shouldParkAfterFailedAcquire()返回false,会继续进行循环。假设node的前继节点pred仍然不是头结点或锁获取失败,则会再次进入shouldParkAfterFailedAcquire()。上一轮循环中,已经将pred.waitStatus设置为SIGNAL==-1,则这次会进入第一个判断条件,直接返回true,表示应该阻塞。
7.parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
很显然,一旦shouldParkAfterFailedAcquire返回true也就是应该阻塞,就会执行parkAndCheckInterrupt()来达到阻塞效果,此时线程阻塞在这里,需要其它线程来唤醒,唤醒后就会再次循环第5步acquireQueued里的请求逻辑。
我们回到第6步,看一个留下的逻辑片段
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
}
什么时候会遇到ws > 0的case呢?当pred所维护的获取请求被取消时(也就是node的waitStatus 值为CANCELLED),这时就会循环移除所有被取消的前继节点pred,直到找到未被取消的pred。移除所有被取消的前继节点后,直接返回false。
8.cancelAcquire()
到这里我们回到第5步可以看到主体逻辑基本走完了,在该方法的finally里有一个cancelAcquire()方法
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
也就是在第5步的执行过程中,如果出现异常或者出现中断,就会执行finally的取消线程的请求操作,核心代码是node.waitStatus = Node.CANCELLED;将线程的状态改为CANCELLED。
1.release()
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
释放独占锁,如果tryRelease成功返回true的话就会解开阻塞等待的线程
显然,tryRelease方法来释放锁,如果释放成功,先判断head节点是否有效,最后unparkSuccessor启动后续等待的线程。
2.tryRelease()
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;
}
先获取state减去释放的一次,然后判断当前线程是否和持有锁线程一致,如果不一致,抛出异常,继续判断state的值,只有当值为0时,free标志才置为true,否则说明是重入锁,需要多次释放直到state为0。
3.unparkSuccessor()
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
这个方法名:启动后续线程,先拿到head节点的waitStatus并清空,然后获取next节点,并做检查,如果next节点失效,就从等待队列的尾部进行轮询,拿到第一个有效的节点,然后通过LockSupport.unpark(s.thread);唤醒,令该线程重新进入到获取锁的第5步循环去acquire锁。