内容简介:最近在学习Java并发包里面各种锁的实现以及AQS框架时,了解到线程阻塞都是通过上述代码在执行的过程中很有可能会在使用
最近在学习 Java 并发包里面各种锁的实现以及AQS框架时,了解到线程阻塞都是通过 LockSupport
这样一个类来实现的。查阅了相关资料发现早期的线程阻塞方式是直接借助 Thread
类的 suspend
方法来完成。然而,现在使用 suspend
以及 resume
方法都会被提醒方法已过期,且容易导致死锁。下面就里面的原因进行简单的探究。
一个Suspend方法导致死锁的经典案例
public class SuspendTest { public static void main(String args[]) throws InterruptedException { Thread t = new Thread(new MyTask()); t.start(); Thread.sleep(100); t.suspend(); System.out.println("resuming"); t.resume(); Thread.sleep(100); t.interrupt(); } public static class MyTask implements Runnable { @Override public void run() { int count = 0; while (!Thread.currentThread().isInterrupted()) { count++; System.out.println("looping: " + count); } } } 复制代码
上述代码在执行的过程中很有可能会在 System.out.println("resuming")
这句语句处进入死锁状态。这是因为 println
方法是一个同步方法,在执行 t.suspend()
时,线程t很有可能已经获取out对象的锁,进入了 println
方法。从而导致主线程获取不到阻塞的t线程占据的锁,进而发生死锁。
使用 suspend
方法带来的不确定性正是因为线程t的阻塞是由外界控制的,也就意味t阻塞的时候执行的代码位置、数据状态、锁的信息都是不能确定的。
public static class MyTask implements Runnable { @Override public void run() { boolean state = true; int count = 0; while (state && !Thread.currentThread().isInterrupted()) { count++; System.out.println("looping: " + count); try { Thread.sleep(10); } catch (InterruptedException ex) { System.out.println(ex); state = false; } } } } 复制代码
我们给任务线程t的 println
方法后面加上一段睡眠时间,这样t被阻塞时可能正在打印内容,也有可能在睡眠。主线程的 println
方法就有可能获取到锁,从而顺利执行下去。改变睡眠的时间,程序是否会发生死锁的概率也会改变。
LockSupport类
我们可以使用 LockSupport
类来实现线程的阻塞与恢复,相关的方法是 park
与 unpark
。
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); } public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); } 复制代码
park
方法可以接收一个阻塞块对象(许可),并关联到阻塞的线程上。 unpark
的参数为需要解除阻塞的线程。我们可以看到 LockSupport
只能够阻塞当前线程,也就意味着线程在何处进入阻塞状态是由自己决定的。下面通过这种方式实现前面的打印程序。
public class SuspendTest { public static void main(String args[]) throws InterruptedException { Thread t = new Thread(new MyTask()); t.start(); Thread.sleep(100); System.out.println("resuming"); LockSupport.unlock(t); Thread.sleep(100); t.interrupt(); } public static class MyTask implements Runnable { @Override public void run() { int count = 0; while (!Thread.currentThread().isInterrupted()) { count++; System.out.println("looping: " + count); LockSupport.lock(this); } } } 复制代码
上述代码没有死锁问题,t线程进入阻塞状态是在 println
执行之后。相比 suspend
方式缺少一定的灵活性,但是线程状态更加具有确定性。线程没有模糊状态,那么死锁的发生就可能是由程序本身的设计带来的,比如我们在线程释放相关锁之前执行 LockSupport.lock()
进入阻塞状态,如下所示,那么死锁就一定会发生。
public class SuspendTest { public static ReentrantLock lock = new ReentrantLock(); public static void main(String args[]) throws InterruptedException { Thread t = new Thread(new MyTask()); t.start(); Thread.sleep(100); lock.lock(); System.out.println("resuming"); lock.unlock(); LockSupport.unlock(t); Thread.sleep(100); t.interrupt(); } public static class MyTask implements Runnable { @Override public void run() { int count = 0; while (!Thread.currentThread().isInterrupted()) { count++; lock.lock(); System.out.println("looping: " + count); LockSupport.lock(this); lock.unlock(); } } } 复制代码
以上所述就是小编给大家介绍的《聊聊线程阻塞与恢复 LockSupport类》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Callable,阻塞队列,线程池问题
- Class.forName 造成的线程阻塞
- 深入理解 Java 锁与线程阻塞
- 15分钟读懂进程线程、同步异步、阻塞非阻塞、并发并行,太实用了!
- 多线程应用--Http请求阻塞回调处理
- Java阻塞问题:为什么JVM会在许多不同的类/方法中阻塞线程?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。