引言

在AQS的实现中,当线程不能拿到锁时,通过调用LockSupport的park方法将线程挂起。这篇文章,我们来看LockSupport这个工具类的实现。提前说一下,LockSupport中的方法都是静态方法,所以它是工具类。

park方法

这个方法会将当前线程挂起。将当前线程挂起,意味着在使用LockSupport这个工具类的时候,线程的挂起只能由该线程自己执行,而不能由其他线程执行,也就是说,线程需要自己将自己挂起,哪个线程调用park方法,哪个线程就会挂起。

调用park方法后线程的状态

我们看下面的例子:

  1. public class LockSupportTest {
  2. private void lockSupport() {
  3. LockSupport.park();
  4. }
  5. public static void main(String[] args) {
  6. LockSupportTest lockSupportTest = new LockSupportTest();
  7. Thread thread = new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. lockSupportTest.lockSupport();
  11. }
  12. }, "park_thread");
  13. thread.start();
  14. }
  15. }

使用jstack命令输出:

  1. "park_thread" #11 prio=5 os_prio=31 tid=0x00007fb908039000 nid=0x5703 waiting on condition [0x0000700008021000]
  2. java.lang.Thread.State: WAITING (parking)
  3. at sun.misc.Unsafe.park(Native Method)
  4. at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
  5. at person.andy.concurrency.lock.LockSupportTest.lockSupport(LockSupportTest.java:7)
  6. at person.andy.concurrency.lock.LockSupportTest.access$000(LockSupportTest.java:5)
  7. at person.andy.concurrency.lock.LockSupportTest$1.run(LockSupportTest.java:14)
  8. at java.lang.Thread.run(Thread.java:748)

可以看到状态是WAITING,注意不是BLOCKING。

park响应中断

线程执行park方法处于WAITING状态后,能对中断作出响应。前面我们在讲Object.wait
方法时说过,线程调用wait方法后也会处于WAITING状态,也会响应中断,响应中断的方式是抛出InterruptedException异常。park方法的响应中断的方式与之不同,它并不会抛出InterruptedException异常,而是直接从park方法返回继续执行。
看下面的例子:

  1. public class LockSupportTest {
  2. private void lockSupport() {
  3. LockSupport.park();
  4. System.out.println("线程继续执行");
  5. }
  6. public static void main(String[] args) throws InterruptedException {
  7. LockSupportTest lockSupportTest = new LockSupportTest();
  8. Thread thread1 = new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. lockSupportTest.lockSupport();
  12. }
  13. }, "park_thread");
  14. thread1.start();
  15. Thread.sleep(3000);
  16. System.out.println("开始中断线程");
  17. thread1.interrupt();
  18. }
  19. }

thread1调用park方法自行挂起之后主线程调用Thread.interrupt()方法对其进行了中断,thread1就会从park方法返回继续执行,也可以认为thread1被以中断的方式唤醒了。
输出如下:

  1. 开始中断线程
  2. 线程继续执行

unpark方法

park方法将线程挂起之后,想要将其唤醒就需要调用unpark方法(前面说的中断也可以认为是唤醒的一种方式)。因为调用park的线程已经被挂起,所以unpark需要其他线程来调用,也就是说,在使用LockSupport类时,线程的挂起是自己挂起的,而线程的唤醒是由其他线程唤醒的。看下面的例子:

  1. public class LockSupportTest {
  2. private void lockSupport() {
  3. LockSupport.park();
  4. System.out.println("线程继续执行");
  5. }
  6. public static void main(String[] args) throws InterruptedException {
  7. LockSupportTest lockSupportTest = new LockSupportTest();
  8. Thread thread1 = new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. lockSupportTest.lockSupport();
  12. }
  13. }, "park_thread");
  14. thread1.start();
  15. Thread.sleep(3000);
  16. System.out.println("开始唤醒线程");
  17. LockSupport.unpark(thread1);
  18. }
  19. }
  1. 开始唤醒线程
  2. 线程继续执行

unpark方法的参数就是需要被唤醒的线程。

ParkNanos(long nanos)和ParkUntil(long deadline)方法

这两个方法对挂起做了时间限制,前者的参数代表线程要挂起的纳秒数,超出时间限制,线程就会自动唤醒。后者表示挂起当前线程直到deadline(1970年到deadline的毫秒数),过了这个时间,也会自动唤醒。
这两个方法就不再举例说明了。

带有object的方法

除了上面说的方法,LockSupport还对每个park方法提供了带有blocker的变体,就是下面这三个方法:

  1. public static void park(Object blocker)
  2. public static void parkNanos(Object blocker, long nanos)
  3. public static void parkUntil(Object blocker, long deadline)

可以看到是对前面的每一个对应的方法增加了一个Object类型的参数,这个参数表示该线程挂起在哪个对象上,这个对象可以用来进行问题排查和系统监控。为了获取线程挂起的对象,LockSupport还提供了getBlocker方法,该方法的参数是被挂起的线程对象。
看下面的例子:

  1. public class LockSupportTest {
  2. private void lockSupport() {
  3. LockSupport.park(this);
  4. }
  5. public static void main(String[] args) throws InterruptedException {
  6. LockSupportTest lockSupportTest = new LockSupportTest();
  7. Thread thread1 = new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. lockSupportTest.lockSupport();
  11. }
  12. }, "park_thread");
  13. thread1.start();
  14. Thread.sleep(3000);
  15. System.out.println(LockSupport.getBlocker(thread1));
  16. }
  17. }

thread1通过park(Object blocker)方法挂起在main方法中创建的LockSupportTest对象上,然后main线程通过调用LockSupport.getBlocker方法获得了thread1挂起的对象,输出如下:

  1. person.andy.concurrency.lock.LockSupportTest@5e2de80c

我们通过jstack命令也能看到park方法挂起在哪个对象上:

  1. "park_thread" #11 prio=5 os_prio=31 tid=0x00007fe99180a000 nid=0x5703 waiting on condition [0x0000700002e59000]
  2. java.lang.Thread.State: WAITING (parking)
  3. at sun.misc.Unsafe.park(Native Method)
  4. - parking to wait for <0x000000076ac2c368> (a person.andy.concurrency.lock.LockSupportTest)
  5. at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
  6. at person.andy.concurrency.lock.LockSupportTest.lockSupport(LockSupportTest.java:8)
  7. at person.andy.concurrency.lock.LockSupportTest.access$000(LockSupportTest.java:5)
  8. at person.andy.concurrency.lock.LockSupportTest$1.run(LockSupportTest.java:15)
  9. at java.lang.Thread.run(Thread.java:748)

第四行说明了挂起在0x000000076ac2c368这个LockSupportTest对象上。