zoukankan      html  css  js  c++  java
  • 线程阻塞工具类:LockSupport

    LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况;和 Object.wait()相比,它不需要先获取某个对象的锁,也不会抛出InterruptedException异常。

    LockSupport接口提供的方法如下:

    1 public static void park();
    2 public static void parkNanos(long nanos);
    3 public static void parkUntil(long deadline);
    4 public static void park(Object blocker);
    5 public static void parkNanos(Object blocker, long nanos);
    6 public static void parkUntil(Object blocker, long deadline);
    7 public static void unpark(Thread thread);

    以上方法的含义如下:

      ❤ park():通过调用本地(Native)方法来阻塞线程;

      ❤ parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒;

      ❤ parkUntil(long deadline):阻塞当前线程,直到deadline;

      ❤ park(Object blocker):阻塞当前线程,记录当前等待对象blocker;

      ❤ parkNanos(Object blocker,long nanos) 和 parkUntil(Object blocker,long deadline) 和上面签名一样的方法一致,只是多了记录等待的对象;

      ❤ unpark(Thread thread):唤醒阻塞的线程;

    下面举例说明LockSupport的用法:

     1 public class LockSupportDemo {
     2     public static Object u = new Object();
     3     static ChangeObjectThread t1 = new ChangeObjectThread("t1");
     4     static ChangeObjectThread t2 = new ChangeObjectThread("t2");
     5 
     6     public static class ChangeObjectThread extends Thread{
     7         public ChangeObjectThread(String name){
     8             super.setName(name);
     9         }
    10 
    11         @Override
    12         public void run() {
    13             synchronized (u){
    14                 System.out.println(" 阻塞线程: " + getName() + "时间:" + System.currentTimeMillis());
    15                 LockSupport.park();
    16                 System.out.println(" 唤醒线程: " + getName() + "时间:" + System.currentTimeMillis());
    17             }
    18         }
    19     }
    20     //测试
    21     public static void main(String[] args) throws InterruptedException {
    22         t1.start();
    23         t2.start();
    24         Thread.sleep(1000);
    25         LockSupport.unpark(t1);
    26         LockSupport.unpark(t2);
    27         t1.join();
    28         t2.join();
    29     }
    30 }

    输出结果:

     阻塞线程: t1时间:1537924774519
     唤醒线程: t1时间:1537924775518
     阻塞线程: t2时间:1537924775518
     唤醒线程: t2时间:1537924775518

    注意:

      上面代码我们无法保证unpark()发生在park()方法之后,也就是说,有可能先执行unpark(),再执行park();但是运行上述代码,你会发现,代码都会运行成功,不会因为park()方法而导致线程永久性的挂起。

    原因:

      这是因为LockSupport类使用了类似信号量的机制。它为每一个线程准备了一个许可,如果许可可用,那么park()函数会立即返回,并且消费这个许可(也就是将许可变为不可用),如果许可不可用,就会阻塞。而unpark()则使得一个许可变为可用(但是和信号量不同的是,许可不能累加,你不能拥有超过一个许可,也就是说一个线程最多只能拥有一个许可),这个特点使得:即使unpark()发生在park()之前,它也可以使下一次的park()操作立即返回;因为执行完unpark()方法后,许可是一定可用的,那么再执行park()方法,park()方法就会立即返回,唤醒线程,这点从上述结果t2的时间戳也可以看出,唤醒和阻塞都是同一时间。

    LockSupport的优点:

      ❤ 即使unpark()发生在park()方法之前,LockSupport也会在下一次的park()方法立即返回,不会导致线程挂起;

      ❤ 处于park()方法挂起的线程的状态是WAITTING,还会标注是park()引起的,这对代码的调试是非常重要的,不会像suspend()方法给出一个令人费解的Runnable状态;

      ❤ 可以使用park(Object blocker)方法,这样可以为当前线程设置一个阻塞对象,并且这个阻塞对象会出现在线程Dump中,分析问题,就会更加方便;

      ❤ 有定时阻塞和阻塞时间自由决定,这样对线程阻塞控制非常灵活;

      ❤ LockSupport.park()支持中断影响,但是不会抛出InterruptedException异常,它会默默的返回,但可以从Thread.interrupted()方法等获得这个中断标记;

    下面举例看一下LockSupport对中断的支持:

     1 public class LockSupportInt {
     2     public static Object u = new Object();
     3     static ChangeThread t1 = new ChangeThread("t1");
     4     static ChangeThread t2 = new ChangeThread("t2");
     5 
     6     public static class ChangeThread extends Thread{
     7         public ChangeThread(String name){
     8             super.setName(name);
     9         }
    10 
    11         @Override
    12         public void run() {
    13             synchronized (u){
    14                 System.out.println("in " + getName());
    15                 LockSupport.park();
    16                 if (Thread.interrupted()){
    17                     System.out.println(getName() + "被中断了!");
    18                 }
    19             }
    20             System.out.println(getName() + "执行结束!");
    21         }
    22     }
    23     //测试
    24     public static void main(String[] args) throws InterruptedException {
    25         t1.start();
    26         Thread.sleep(1000);
    27         t2.start();
    28         t1.interrupt();
    29         LockSupport.unpark(t2);
    30     }
    31 }

    输出结果:

    in t1
    t1被中断了!
    t1执行结束!
    in t2
    t2执行结束!

    上述代码在第28行,中断了处于park()状态的t1。t1响应了这个中断,并且返回。之后在外面等待对象u锁的t2才进入临界区,并由代码第29行,唤醒t2,使其操作结束。这点在输出结果中也可以看出。

    挂起(suspend)和继续执行(resume)

    这两个方法也是对线程进行阻塞和唤醒的,目前所有的IDE都已不推荐使用。下面就简单介绍下这两个方法的缺点,即为什么不推荐使用:

      ①suspend()方法在挂起线程的同时,不会释放任何资源,这样导致如果其他线程想要访问它占用的锁资源时,都会被阻塞;

      ②被suspend()方法挂起的线程的状态是RUNNABLE,这会对我们排查系统的问题时,造成很大的迷惑性,不利于排查问题;

      ③线程挂起suspend()和线程唤醒resume()方法必须严格控制顺序,即先挂起,再唤醒。若反之,则会导致线程永远挂起;

     将上述演示LockSupport的代码换为这两个方法,就会看出问题所在:

     1 public class LockSupportDemo {
     2     public static Object u = new Object();
     3     static ChangeObjectThread t1 = new ChangeObjectThread("t1");
     4     static ChangeObjectThread t2 = new ChangeObjectThread("t2");
     5 
     6     public static class ChangeObjectThread extends Thread{
     7         public ChangeObjectThread(String name){
     8             super.setName(name);
     9         }
    10 
    11         @Override
    12         public void run() {
    13             synchronized (u){
    14                 System.out.println(" 阻塞线程: " + getName() + "时间:" + System.currentTimeMillis());
    15                // LockSupport.park();
    16                 Thread.currentThread().suspend();
    17                 System.out.println(" 唤醒线程: " + getName() + "时间:" + System.currentTimeMillis());
    18             }
    19         }
    20     }
    21     //测试
    22     public static void main(String[] args) throws InterruptedException {
    23         t1.start();
    24         t2.start();
    25         Thread.sleep(1000);
    26         //LockSupport.unpark(t1);
    27         //LockSupport.unpark(t2);
    28         t1.resume();
    29         t2.resume();
    30         t1.join();
    31         t2.join();
    32     }
    33 }

    输出结果:

     阻塞线程: t1时间:1537929982937
     唤醒线程: t1时间:1537929983937
     阻塞线程: t2时间:1537929983937

    运行代码,发现并没有唤醒线程t2,导致线程t2永远挂起,并且程序不会退出,这就是由于resume()和suspend()方法调用顺序出错导致resume()方法并没有生效,并且t2永远占用了u对象,如果发生在系统中,这可能是致命的。

    参考:《Java高并发程序设计》 葛一鸣 郭超 编著;

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    java 虚拟机启动参数[转]
    Android SDK Manager 无法下载更新,或者更新速度超慢,或者待安装包列表不显示
    fluentnhibernet auto mapping
    取消sqlserver 锁表
    TFS 2010 配置的时候,提示TF255466错误
    doc中文乱码的解决方法 中英文切换
    silverlight 读取wcf服务 读取宿主端的config 良好的方法
    dojo+js+html5学习网址
    win 7 64位 配置silverlight 32位的应用程序(sl网站)
    HTTP协议及其POST与GET操作差异 & C#中如何使用POST、GET等
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9705626.html
Copyright © 2011-2022 走看看