多线程工具类 - LockSupport

LockSupport解决了什么问题:LockSupport使用静态方法可以让线程在任意位置阻塞, 当然也可以重新唤醒

针对线程的阻塞和重新唤醒, 有很多种方法, 其中基础方式有以下几种(重入锁等高级封装方式不在此文考虑)

 

1.Object自有的wait和notify

但是这种方式使用起来比较麻烦,需要获取辅助对象(以下例子的lock对象)的监听器,并且notify为随机唤醒,而notifyAll则是唤醒所有辅助对象

public static void main(String[] args) throws InterruptedException {
    String lock = "111";
    Thread t = new Thread(()->{
        synchronized (lock){
            try {
                //无限等待 直到被notify
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程执行完");
    }, "测试线程");
    t.start();
    System.out.println("解锁");
    synchronized (flag){
        flag.notifyAll();
    }
    System.out.println("完成解锁");
}

执行结果:
解锁
完成解锁
线程执行完

 

2.使用Thread的挂起和唤醒方法:  Thread.suspend() 挂起,Thread.resume() 唤醒,目前JDK注解为弃用, 但是如果无法保证resume()在suspend()之后执行,则线程将永远处于挂起状态,如果挂起线程中有同步模块,

    后果很严重,被锁定的对象也将永远被释放

//错误示例: 下面模拟的是一个主线程被挂起的代码
public static void main(String[] args) throws InterruptedException {
    Object lockObject = new Object();
    Thread t1=new Thread(()->{
        System.out.println("t1 准备挂起");
        try {
            //模拟一个3秒的业务执行
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread.currentThread().suspend();
        System.out.println("t1 结束等待");

    },"t1");
    t1.start();
    //由于挂起之前模拟了一个3秒的等待 会导致唤醒先执行,挂起后执行,最终永远挂起
    t1.resume();
}

结果:
t1 准备挂起

 

3 使用LockSupport的静态方法进行 阻塞park()  和 唤醒unPark(Thread),  使用场景为可以获取线程对象时使用, 从方法unPark(Thread)很容易看出, 线程需要被其他线程(包括主线程)持有时才能解锁

public static void main(String[] args) throws InterruptedException {
    Object lockObject = new Object();
    Thread t1=new Thread(()->{
        System.out.println("t1 准备挂起");
        try {
            //模拟一个3秒的业务执行
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LockSupport.park();
        System.out.println("t1 结束等待");

    },"t1");

    t1.start();
    LockSupport.unpark(t1);
    System.out.println("挂起前就解锁");
}

结果:
t1 准备挂起
挂起前就解锁
t1 结束等待

从结果可以看出LockSupport先执行解锁然后执行唤醒, 不会影响最终的执行流程,虽然park()方法后执行 但是任然可以唤醒

接下来再看一段代码

public static void main(String[] args) throws InterruptedException {
    Object lockObject = new Object();
    Thread t1=new Thread(()->{
        System.out.println("t1 准备挂起");
        try {
            //模拟一个3秒的业务执行
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LockSupport.park();
        System.out.println("第一次被唤醒");
        LockSupport.park();
        System.out.println("第二次被唤醒");

    },"t1");

    t1.start();
    LockSupport.unpark(t1);
    LockSupport.unpark(t1);
    System.out.println("挂起前就解锁");
}

结果:
t1 准备挂起
挂起前就解锁
第一次被唤醒

从执行结果可以看到 提前执行两次unpark(t1), 但是只有第一个park()被唤醒, 第二个park()依旧阻塞线程,  接着我们思考若是两个park()都在unpark(t1)之前执行又将是怎样结果, 继续看代码

public static void main(String[] args) throws InterruptedException {
    Object lockObject = new Object();
    Thread t1=new Thread(()->{
        System.out.println("t1 准备挂起");
        LockSupport.park();
        System.out.println("第一次被唤醒");
        LockSupport.park();
        System.out.println("第二次被唤醒");

    },"t1");
    t1.start();
    //保证t1第一次挂起 再解锁
    Thread.sleep(1000L);
    LockSupport.unpark(t1);
    //保证t1第一次挂起 再解锁
    Thread.sleep(1000L);
    LockSupport.unpark(t1);
    System.out.println("挂起前就解锁");
}


结果:
t1 准备挂起
第一次被唤醒
挂起前就解锁
第二次被唤醒

上面代码执行顺序为  挂起->解锁->唤醒   ->挂起->解锁->唤醒

LockSupport原理: park消费一个许可, unpark提供一个许可, 通过信号量来判断是否继续, 若是这个许可未被消费, 则unpark时会检查是否许可是否存在,若存在则跳过提供许可, 不存在则提供一个,

相同原理, park消费许可, 若存在许可则消费掉, 并继续往下执行, 若不存在许可则阻塞当前线程直到许可提供为止

 注:若是需要线程间通信,相较于使用object.wait和notity,  使用基于LockSupport实现的 Reentrantlock Condition更方便, 功能更强, 属于多线程的高级应用,后续Reentrantlock将会讲到

 

 

总结: 1 可以持有阻塞线程对象时使用LockSupport 编码量更低, 使用更简便

         2 无法持有阻塞线程对象时, 则需要通过共享对象作为阻塞和唤醒对象, 则使用 wait和notify 方式更合理

         3 LockSupport效率更高, 在park时检测是否有许可判定是否继续往下执行, unpark时则判定线程是否被挂起,因为持有线程对象,只需要将线程重新唤醒, 相比CPU自旋方式显然效率上更优

posted @ 2020-01-07 14:35  蟹烟客  阅读(241)  评论(0编辑  收藏  举报