LockSupport源码解析

LockSupport源码解析

LockSupport是JDK在rt包里面提供的一个工具类,它的主要作用是挂起和唤醒线程。LockSupport是创建锁和其他同步类的基础。

LockSupport类与每个使用它的线程都会关联一个许可(permit),permit的值只有0或1,默认是0.

  • 当前用unpark(thread)方法时,会将指定线程的许可permit设置为1。unpark方法是不可重入的,多次调用之后,许可permit的值还是为1.
  • 当调用park方法时,如果当前线程的许可permit是1,那么将许可permit设置为0,并立即返回。如果当前线程的许可permit是0,那么当前线程就会阻塞,直到别的线程将当前线程的许可permit设置为1.park方法会将许可permit再次设置为0,并返回。
  • 在未调用过unpark方法之前调用park方法,因为许可permit为0,调用线程会被阻塞,调用unpark方法之后,线程才会被唤醒。
  • 线程如果被中断了,也会从park的阻塞方法处被唤醒
  • 还有一种线程从park方法处返回的可能是,不知道为什么,线程自己就返回了。

LockSupport使用示例

首先,尝试如下代码:

1
2
3
4
5
6
7
8
9
10
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("Before park");

LockSupport.park();

System.out.println("After park");
}
}

在调用unPark之前,调用park方法,可以看到,当前只输出了Before park之后就不再输出了,这说明,当前线程确实已经被阻塞住了。

接着我们把上面的例子再改一下,在调用park之前,调用unpark先:

1
2
3
4
5
6
7
8
9
10
11
12
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("Before park");

LockSupport.unpark(Thread.currentThread());

LockSupport.park();

System.out.println("After park");
}
}

发现,能正常打印出两个输出语句。

接着再看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Child thread before park");

LockSupport.park();

System.out.println("Child thread after park");
}
});

thread.start();

Thread.sleep(1000);

System.out.println("Main thread begin unpark");

LockSupport.unpark(thread);
}

上面的例子中,新起了一个线程thread,然后在它的run方法中调用了LockSupport的park方法,因为之前thread并没有调用过unpark方法,因此,线程thread会阻塞在park方法这。接着,主线程在睡眠1秒之后,开始调用unpark方法,这个时候,线程thread就会从阻塞处恢复,接着打印出Child thread after park

运行一下,看下效果:

最后再看看,线程被中断之后,从park出返回的例子,代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("Child thread before park");

while (!Thread.currentThread().isInterrupted()) {
LockSupport.park();
}

System.out.println("Child thread after park");
}
});

thread.start();

Thread.sleep(1000);

System.out.println("Main thread begin unpark");

// 中断线程
thread.interrupt();
}

代码逻辑很简单,就不再赘述,直接看运行结果:

最后,我们再看一个JDK提供的LockSupport方法的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

// 简易的先进先出非重入锁
class FIFOMutex {
//
private final AtomicBoolean locked = new AtomicBoolean(false);
// 记录等待线程队列
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();

public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
waiters.add(current);

// 如果当前线程不是等待线程队列第一个,或者locked状态已经是true,那么当前线程就要等待
while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
System.out.println(Thread.currentThread().getName()+" park start");
LockSupport.park(this);
System.out.println(Thread.currentThread().getName()+" park end");
// 等待线程的中断线程标志位为true,就设置wasInterrupted为true
if (Thread.interrupted())
wasInterrupted = true;
}

// 移除第一个元素。当前线程就是第一个元素,因为while判断条件
waiters.remove();
// 如果wasInterrupted为true,当前线程发出中断请求
if (wasInterrupted)
current.interrupt();
System.out.println(Thread.currentThread().getName()+" lock end" );
}

// 唤醒可能等待的线程
public void unlock() {
System.out.println(Thread.currentThread().getName()+" unpark start ");
// 将locked设置为false
locked.set(false);
// 唤醒当前线程队列中第一个元素
LockSupport.unpark(waiters.peek());
System.out.println(Thread.currentThread().getName()+" unpark end ");
}
}

LockSupport常用方法

park

如果调用park方法的线程已经拿到了与LockSupport关联的许可permit,则调用park方法时会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。

unpark(Thread thread)

当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类关联的许可permit,则让thread持有。如果thread之前因调用park而被阻塞挂起,则调用unpark后,线程会被唤醒。如果thread之前没有调用park,则调用unpark后,在调用park方法,其会立刻返回。

parkNanos(long nanos)

如果调用park的方法已经拿到了与LockSupport关联的许可permit,则调用LockSupport.parkNanos方法后会马上返回。该方法的不同在于,如果没有拿到许可permit,则调用线程会被挂起nanos时间后修改为自动返回。

park(Object blocker)

当线程在没有持有许可permit的情况下调用park方法而被阻塞挂起时,这个block对象会被记录到该线程的内部。

使用诊断工具可以观察线程被阻塞的原因,诊断工具是通过调用getBlocker(Thread)方法来获取blocker对象的,所以JDK推荐我们使用带有blocker参数的park方法,并且blocker设置为this,这样当在打印线程堆栈排查问题时就能知道是哪个类被阻塞了。

参考资料

Java并发编程之美

0%