LockSupport
LockSupport 的功能
LockSupport 是JDK 中提供的一个工具类,用来挂起和唤醒线程,这个类是 JDK 中所有同步类的基础,JDK 中 AQS 的实现也是基于此;
LockSupport 类是通过操作 Unsafe 类来实现的,而线程在使用时,被挂起和唤醒都是因为这个类的两个方法,即 park 方法和 unpark 方法,他们通过让线程是否关联一个许可证来实现的;
也就是说如果线程通过 LockSupport 类的方法调用,被关联了一个许可证那么线程会被唤醒,如果没有关联则会挂起等待;
方法功能介绍
park()
首先直接给出源码:
public static void park() { UNSAFE.park(false, 0L); }
park 方法是一个 static 方法,并且没有入参也没有返回值,它调用了 Unsafe 的方法,这个是一个 native 方法,并且当前线程与 LockSupport 类不会关联一个许可证;
当调用 park 方法后,由于默认是没有持有许可证,因此当前线程会被挂起阻塞;
unpark(Thread thread)
首先直接给出源码:
public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }
unpark 方法也是一个 static 方法,它的入参是一个 Thread 对象返回值也为空,同样的也是调用了 Unsafe 类的方法,它会为入参线程和 LockSupport 类关联一个许可证;
当 unpark 方法被调用后,如果入参的 thread 线程没有和 LockSupport 类关联许可证,那么调用后就会被关联一个许可证,因此入参的 thread 线程会被唤醒;
测试 demo
public static void main(String[] args) { System.out.println("main start"); Thread thread = new Thread(() -> { System.out.println("thread start"); LockSupport.park(); System.out.println("thread end"); }); thread.start(); try { System.out.println("main sleep start test park"); Thread.sleep(100); System.out.println("main sleep end test park"); } catch (InterruptedException e) { e.printStackTrace(); } LockSupport.unpark(thread); try { System.out.println("main sleep start test unpark"); Thread.sleep(100); System.out.println("main sleep end test unpark"); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main end"); }
结果
main start
main sleep start test park
thread start
main sleep end test park
main sleep start test unpark
thread end
main sleep end test unpark
main end
上面的代码,创建了一个线程 thread 在线程中调用了 park 方法,因此 thread 线程会被挂起,当在 main 线程中,调用了 unpark 后,thread 线程会被唤醒;
parkNanos(long nanos)
public static void parkNanos(long nanos) { if (nanos > 0) UNSAFE.park(false, nanos); }
和 park 方法功能一样,只是这个是带有时间,park 方法的时间传的 0,当线程没有和 LockSupport 关联许可证,一样会被挂起,不同的是它在被挂起 nanos 时间胡会返回,就是有一个超时时间,在达到时间后即使没有获得和 LockSupport 关联的许可证也会返回;
测试demo
public static void main(String[] args) { System.out.println("start"); LockSupport.parkNanos(10000000000L); // LockSupport.park(); System.out.println("end"); }
上面的代码分别测试了 parkNanos 和 park 方法,park 会一会阻塞,而 parkNanos 阻塞一定时间后就返回;
park(Object blocker) & getBlocker(Thread t)
public static void park(Object blocker) { Thread t = Thread.currentThread(); //阻塞前设置blocker setBlocker(t, blocker); UNSAFE.park(false, 0L); //结束阻塞后把设置的blocker 设置为空 setBlocker(t, null); } //获取 Thread 类中变量 parkBlocker 的偏移量 Class<?> tk = Thread.class; parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker")); //通过Unsafe 类把 Thread 对象中偏移量为 parkBlockerOffset 的属性设置为 blocker private static void setBlocker(Thread t, Object arg) { // Even though volatile, hotspot doesn't need a write barrier here. UNSAFE.putObject(t, parkBlockerOffset, arg); } public static Object getBlocker(Thread t) { if (t == null) throw new NullPointerException(); return UNSAFE.getObjectVolatile(t, parkBlockerOffset); }
首先来看 park(Object blocker) 方法,这个方法其实和 park 方法一样,都是调用了 Unsafe 类的 park 方法;
只是在调用前将入参的 blocker 对象设置到了当前线程的 parkBlocker 变量中(在线程的 Thread 类中有一个 volatile 的 变量 parkBlocker);
因此 park(Object blocker) 方法相对于 park() 方法的优势在于,它在当前线程中记录了 blocker 对象,而通常我们的 blocker 对象就是当前对象也就是 this;而这种方式也正是 JDK 推荐的方式;
通过 getBlocker(Thread t) 可以获取 线程 t 中属性 parkBlocker 的值;
测试demo
public static void main(String[] args) { ThreadTest t = new ThreadTest(); t.testBlocker(); } //直接将this传入即可 public void testBlocker (){ LockSupport.park(this); }
使用这种方式还有一个好处,就是当线程被阻塞后,通过 jstack 打印堆栈信息时,可以看到我们设置的 this 对象,也就能看到阻塞的对象,这样能在堆栈中查到更多信息,方便问题的定位及解决;
parkNanos(Object blocker, long nanos)
public static void parkNanos(Object blocker, long nanos) { if (nanos > 0) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, nanos); setBlocker(t, null); } }
这个方法和 park(Object blocker) 类似只是多了一个超时时间,但是如果时间小于等于0 将没有任何作用