zoukankan      html  css  js  c++  java
  • 多线程工具类

    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自旋方式显然效率上更优

  • 相关阅读:
    微信分享相关
    移动端界面适配
    MongoDB安装使用
    MongoDB可视化工具RoboMongo
    PhotoSwipe图片展示插件
    BootStrap下拉框搜索功能
    Node.js 特点
    原生node实现本地静态页面的展示
    阿里巴巴电话初面
    react动态添加多个输入框
  • 原文地址:https://www.cnblogs.com/xieyanke/p/12161400.html
Copyright © 2011-2022 走看看