java线程阻塞LockSupport.park()和Thread.sleep()、Object.wait()的区别

2020-07-13 15:24:48 | 编辑

在java语言中,可以通过3种方式让线程进入休眠状态,分别是Thread.sleep()、Object.wait()、LockSupport.park()方法。这三种方法的表现和原理都各有不同.

1.LockSupport介绍

    Thread.sleep()、Object.wait()是比较常用的线程阻塞方法,其实LockSupport也是鼎鼎有名的。LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。

LockSupport.park 的实现原理是通过二元信号量做的阻塞,要注意的是,这个信号量最多只能加到1。我们也可以理解成获取释放许可证的场景。unpark()方法会释放一个许可证,park()方法则是获取许可证,如果当前没有许可证,则进入休眠状态,知道许可证被释放了才被唤醒。无论执行多少次unpark()方法,也最多只会有一个许可证。

LockSupport并不需要获取对象的监视器。LockSupport机制是每次unpark给线程1个"许可"——最多只能是1,而park则相反,如果当前线程有许可,那么park方法会消耗1个并返回,否则会阻塞线程直到线程重新获得许可,在线程启动之前调用 park/unpark方法没有任何效果。因为它们本身的实现机制不一样,所以它们之间没有交集,也就是说LockSupport阻塞的线程,notify/notifyAll没法唤醒.

     Java锁和同步器框架的核心AQS:AbstractQueuedSynchronizer,就是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的。LockSupport很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继续执行;如果许可已经被占用,当前线程阻塞,等待获取许可。

     LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。因为park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

LockSupport函数列表

// 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
static Object getBlocker(Thread t)
// 为了线程调度,禁用当前线程,除非许可可用。
static void park()
// 为了线程调度,在许可可用之前禁用当前线程。
static void park(Object blocker)
// 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
static void parkNanos(long nanos)
// 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
static void parkNanos(Object blocker, long nanos)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(long deadline)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(Object blocker, long deadline)
// 如果给定线程的许可尚不可用,则使其可用。
static void unpark(Thread thread)

说明:LockSupport是通过调用Unsafe函数中的接口实现阻塞和解除阻塞的。

 

2.sleep

sleep(millis)意思就是睡眠,通过jstack输出线程快照的话此时该线程的状态应该是TIMED_WAITING,表示休眠一段时间。

线程会等待传入的时间,直到时间到或者中断(调用interrupt方法抛出interrupt异常)才继续执行,所以他不依赖监视器和对象锁,因此也不存在释放cpu资源,通过sleep方法进入休眠的线程不会释放持有的锁,因此,在持有锁的时候调用该方法需要谨慎。该方法会抛出InterruptedException异常,这是受检查异常,调用者必须处理。

 

3.wait-notify

通过object.wait()方法也可以让线程进入休眠。如果不传timeout,wait将会进入无限制的休眠当中,直到有人唤醒他。使用wait()让线程进入休眠的话,无论有没有传入timeout参数,线程的状态都将是WAITING状态。

wait() 在调用对象的Wait之前当前线程必须先获得该对象的监视器(Synchronized),则程序会在运行时抛出IllegalMonitorStateException异常。调用wait()方法后,线程进入休眠的同时,会释放持有的该对象的锁,这样其他线程就能在这期间获取到锁了。

被唤醒之后需要重新获取到监视器和cpu才能继续执行。wait后会释放cpu资源,我们可以通过调用正在等待的监视器上的notify()或notifyAll()方法来唤醒线程。

 

4.park和sleep、wait的区别

4.1park、unpark方法和wait、notify()方法有一些相似的地方。都是休眠,然后唤醒。但是wait、notify方法有一个不好的地方,就是我们在编程的时候必须能保证wait方法比notify方法先执行。如果notify方法比wait方法晚执行的话,就会导致因wait方法进入休眠的线程接收不到唤醒通知的问题。而park、unpark则不会有这个问题,我们可以先调用unpark方法释放一个许可证,这样后面线程调用park方法时,发现已经许可证了,就可以直接获取许可证而不用进入休眠状态了。
 

4.2但是有一点, LockSupport 的park和Object的wait一样也能响应Thread.interrupted()中断.但park方法不会抛出InterruptedException

 

登录后即可回复 登录 | 注册
    
关注编程学问公众号