并发编程04:LockSupport与线程中断

news2024/11/15 8:36:22

文章目录

  • 4.1 线程中断机制
    • 4.1.1 从阿里蚂蚁金服面试题讲起
    • 4.1.2 什么是中断机制
    • 4.1.3 中断的相关API方法之三大方法说明
    • 4.1.4 大厂面试题中断机制考点
    • 4.1.5 总结
  • 4.2 LockSupport是什么
  • 4.3 线程等待唤醒机制
    • 4.3.1 三种让线程等待和唤醒的方法
    • 4.3.2 Object类中的wait和notify方法实现线程等待和唤醒
    • 4.3.3 Condition接口中的await和signal方法实现线程的等待和唤醒
    • 4.3.4 上述两个对象Object和Condition使用的限制条件
    • 4.3.5 LockSupport类中的park等待和unpark唤醒

4.1 线程中断机制

4.1.1 从阿里蚂蚁金服面试题讲起

Java.lang.Thread下的三个方法:

在这里插入图片描述

  • 如何中断一个运行中的线程?
  • 如何停止一个运行中的线程?

4.1.2 什么是中断机制

  • 首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运,所以,Thread.stop,Thread.suspend,Thread.resume都已经被废弃了
  • 其次,在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制----中断,也即中断标识协商机制
    • 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自行实现。若要中断一个线程,你需要手动调用该线程interrupt方法,该方法也仅仅是将该线程对象的中断标识设置为true,接着你需要自己写代码不断检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竟应该做什么需要你自己写代码实现。
    • 每个线程对象都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设置为true;可以在别的线程中调用,也可以在自己的线程中调用。

4.1.3 中断的相关API方法之三大方法说明

  • public void interrupt()
    • 实例方法 Just to set the interrupt flag
    • 实例方法仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程
  • public static boolean interrupted()
    • 静态方法 Thread.interrupted();
    • 判断线程是否被中断并清除当前中断状态(做了两件事情
      • 1.返回当前线程的中断状态,测试当前线程是否已被中断
      • 2.将当前线程的中断状态清零并重新设置为false,清除线程的中断状态
      • 3.这个方法有点不好理解在于如果连续两次调用此方法,则第二次返回false,因为连续调用两次的结果可能不一样
  • public boolean isInterrupted()
    • 实例方法
    • 判断当前线程是否被中断(通过检查中断标志位)

4.1.4 大厂面试题中断机制考点

如何停止中断运行中的线程?

  1. 通过一个volatile变量实现
/**
 * 使用volatile修饰一个标识符来决定是否结束线程
 */
public class InterruptDemo {
    static volatile boolean isStop = false; //volatile表示的变量具有可见性

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (isStop) {
                    System.out.println(Thread.currentThread().getName() + " isStop的值被改为true,t1程序停止");
                    break;
                }
                System.out.println("-----------hello volatile");
            }
        }, "t1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            isStop = true;
        }, "t2").start();

    }
}
/**
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * -----------hello volatile
 * t1 isStop的值被改为true,t1程序停止
 */

  1. 通过AutomicBoolean
/**
 * 使用AtomicBoolean
 */
public class InterruptDemo {
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);


    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (atomicBoolean.get()) {
                    System.out.println(Thread.currentThread().getName() + " atomicBoolean的值被改为true,t1程序停止");
                    break;
                }
                System.out.println("-----------hello atomicBoolean");
            }
        }, "t1").start();
        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            atomicBoolean.set(true);
        }, "t2").start();

    }
}

/**
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * -----------hello atomicBoolean
 * t1 atomicBoolean的值被改为true,t1程序停止
 */

  1. 通过Thread类自带的中断API实例方法实现----在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑stop线程。
/**
 * 使用interrupt() 和isInterrupted()组合使用来中断某个线程
 */
public class InterruptDemo {
    static AtomicBoolean atomicBoolean = new AtomicBoolean(false);


    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " isInterrupted()的值被改为true,t1程序停止");
                    break;
                }
                System.out.println("-----------hello isInterrupted()");
            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.MILLISECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //t2向t1放出协商,将t1中的中断标识位设为true,希望t1停下来
        new Thread(() -> t1.interrupt(), "t2").start();

        //当然,也可以t1自行设置
        t1.interrupt();

    }
}
/**
 * -----------hello isInterrupted()
 * -----------hello isInterrupted()
 * -----------hello isInterrupted()
 * -----------hello isInterrupted()
 * t1 isInterrupted()的值被改为true,t1程序停止
 */
  • 当前线程的中断标识为true,是不是线程就立刻停止?
    答案是不立刻停止,具体来说,当对一个线程,调用interrupt时:
    • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已,被设置中断标志的线程将继续正常运行,不受影响,所以interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行,对于不活动的线程没有任何影响。
    • 如果线程处于阻塞状态(例如sleep,wait,join状态等),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态(interrupt状态也将被清除),并抛出一个InterruptedException异常。

第一种情况正常活动状态演示

/**
 * 执行interrupt方法将t1标志位设置为true后,t1没有中断,仍然完成了任务后再结束
 * 在2000毫秒后,t1已经结束称为不活动线程,设置状态为没有任何影响
 */
public class InterruptDemo2 {
    public static void main(String[] args) {
        //实例方法interrupt()仅仅是设置线程的中断状态位为true,不会停止线程
        Thread t1 = new Thread(() -> {
            for (int i = 1; i <= 300; i++) {
                System.out.println("------: " + i);
            }
            /**
             * ------: 298
             * ------: 299
             * ------: 300
             * t1线程调用interrupt()后的中断标志位02:true
             */
            System.out.println("t1线程调用interrupt()后的中断标志位02:" + Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();

        System.out.println("t1线程默认的中断标志位:" + t1.isInterrupted());//false

        try {
            TimeUnit.MILLISECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t1.interrupt();//true
        /**
         * ------: 251
         * ------: 252
         * ------: 253
         * t1线程调用interrupt()后的中断标志位01:true
         */
        System.out.println("t1线程调用interrupt()后的中断标志位01:" + t1.isInterrupted());//true

        try {
            TimeUnit.MILLISECONDS.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2000毫秒后,t1线程已经不活动了,不会产生任何影响
        System.out.println("t1线程调用interrupt()后的中断标志位03:" + t1.isInterrupted());//false

    }
}

第二种情况线程处于阻塞状态演示

/**
 * 1. 中断标志位默认为false
 * 2.t2对t1发出中断协商  t1.interrupt();
 * 3. 中断标志位为true: 正常情况 程序停止
 *    中断标志位为true  异常情况,.InterruptedException ,将会把中断状态清楚,中断标志位为false
 * 4。需要在catch块中,再次调用interrupt()方法将中断标志位设置为false;
 */
public class InterruptDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + " 中断标志位为:" + Thread.currentThread().isInterrupted() + " 程序停止");
                    break;
                }
                //sleep方法抛出InterruptedException后,中断标识也被清空置为false,如果没有在
                //catch方法中调用interrupt方法再次将中断标识置为true,这将导致无限循环了
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    //Thread.currentThread().interrupt(); 
                    e.printStackTrace();
                }
                System.out.println("-------------hello InterruptDemo3");

            }
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            t1.interrupt();
        }, "t2").start();
    }
}

对于第二种情况的源码分析如下;
在这里插入图片描述
总之,需要记住的是中断只是一种协商机制,修改中断标识位仅此而已,不是立刻stop打断

静态方法Thread.interrupted(),谈谈你的理解?

在这里插入图片描述

public class InterruptDemo4 {
    public static void main(String[] args) {
        /**
         * main	false
         * main	false
         * -----------1
         * -----------2
         * main	true
         * main	false
         */
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//false
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//false
        System.out.println("-----------1");
        Thread.currentThread().interrupt();
        System.out.println("-----------2");
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//true
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted());//false

    }
}

对于静态方法Thread.interrupted()和实例方法isInterrupted()区别在于:

  • 静态方法interrupted将会清除中断状态(传入的参数ClearInterrupted为true)
  • 实例方法isInterrupted则不会(传入的参数ClearInterrupted为false)
    在这里插入图片描述

4.1.5 总结

  • public void interrupt() 是一个实例方法,它通知目标线程中断,也仅仅是设置目标线程的中断标志位为true
  • public boolean isInterrupted() 是一个实例方法,它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志
  • public static boolean interrupted() 是一个静态方法,返回当前线程的中断真实状态(boolean类型)后会将当前线程的中断状态设为false,此方法调用之后会清楚当前线程的中断标志位的状态(将中断标志置为false了),返回当前值并清零置为false。

4.2 LockSupport是什么

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,其中park()和unpack()而作用分别是阻塞线程和解除阻塞线程.

4.3 线程等待唤醒机制

4.3.1 三种让线程等待和唤醒的方法

  • 方式一:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  • 方式二:使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
  • 方式三:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

4.3.2 Object类中的wait和notify方法实现线程等待和唤醒

  • wait和notify方法必须要在同步代码块或者方法里面,且成对出现使用
  • 先wait再notify才ok
public class LockSupportDemo {

    public static void main(String[] args) {
        Object objectLock = new Object();
        /**
         * t1	 -----------come in
         * t2	 -----------发出通知
         * t1	 -------被唤醒
         */
        new Thread(() -> {
            synchronized (objectLock) {
                System.out.println(Thread.currentThread().getName() + "\t -----------come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");
            }
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
            }

        }, "t2").start();
    }
}

4.3.3 Condition接口中的await和signal方法实现线程的等待和唤醒

  • Condition中的线程等待和唤醒方法,需要先获取锁
  • 一定要先await后signal,不要反了
public class LockSupportDemo {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        /**
         * t1	 -----------come in
         * t2	 -----------发出通知
         * t1	 -----------被唤醒
         */
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t -----------come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
            } finally {
                lock.unlock();
            }
        }, "t2").start();

    }
}

4.3.4 上述两个对象Object和Condition使用的限制条件

  • 线程需要先获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒

4.3.5 LockSupport类中的park等待和unpark唤醒

  • 是什么
    • LockSupport 是用于创建锁和其他同步类的基本线程阻塞原语
    • LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(Permit),许可证只能有一个,累加上限是1
  • 主要方法
    • 阻塞: Peimit许可证默认没有不能放行,所以一开始调用park()方法当前线程会阻塞,直到别的线程给当前线程发放peimit,park方法才会被唤醒。
      • park/park(Object blocker)-------阻塞当前线程/阻塞传入的具体线程
    • 唤醒: 调用unpack(thread)方法后 就会将thread线程的许可证peimit发放,会自动唤醒park线程,即之前阻塞中的LockSupport.park()方法会立即返回。
      • unpark(Thread thread)------唤醒处于阻塞状态的指定线程
  • 代码
public class LockSupportDemo {

    public static void main(String[] args) {
        /**
         * t1	 -----------come in
         * t2	 ----------发出通知
         * t1	 ----------被唤醒
         */
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t -----------come in");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
        }, "t1");
        t1.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
        }, "t2").start();

    }
}
  • 重点说明(重要)
    • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,所有的方法都是静态方法,可以让线程再任意位置阻塞,阻塞后也有对应的唤醒方法。归根结底,LockSupport时调用Unsafe中的native代码
    • LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程,LockSupport和每个使用它的线程都有一个许可(Peimit)关联,每个线程都有一个相关的permit,peimit最多只有一个,重复调用unpark也不会积累凭证
    • 形象理解:线程阻塞需要消耗凭证(Permit),这个凭证最多只有一个
      • 当调用park时,如果有凭证,则会直接消耗掉这个凭证然后正常退出。如果没有凭证,则必须阻塞等待凭证可用;
      • 当调用unpark时,它会增加一个凭证,但凭证最多只能有1各,累加无效。
  • 面试题
    • 为什么LockSupport可以突破wait/notify的原有调用顺序?
      • 因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以畅通无阻。
    • 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
      • 因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证,而调用两次park却需要消费两个凭证,证不够,不能放行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/498498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uboot命令体系 源码解读并从中添加命令

1、uboot命令体系基础 1.1、使用uboot命令 (1)uboot启动后进入命令行底下&#xff0c;在此输入命令并回车结束&#xff0c;uboot会接收这个命令并解析&#xff0c;然后执行。 1.2、uboot命令体系实现代码在哪里 (1)uboot命令体系的实现代码在uboot/common/下&#xff0c;其…

【博学谷学习记录】超强总结,用心分享 | 架构师 MongoDB学习总结

文章目录 MongoDB基本使用Nosql简介什么是NoSQL为什么使用NoSQLRDBMS vs NoSQLNoSQLNoSQL的优缺点缺点 MongoDB基础什么是MongoDB存储结构主要特点 基本概念和传统数据库对比集合命令规范 文档key的命令规范注意事项 MongoDB的数据类型BSON支持的数据类型 MongoDB基本使用 Nos…

DT MongoDB Plug -in description

目录 DT MongoDB | Client Create MongoDB Client Connect By Url Get Collection DT MongoDB | Collection Insert One Insert Many Delete One Delete Many Replace One Update One Update Many Find Find One DT MongoDB | Document Create MongoDB Documen…

【算法与数据结构】顺序表

顺序表 数据结构 结构定义结构操作 顺序表&#xff1a;结构定义 一个数组&#xff0c;添加额外的几个属性&#xff1a;size, count等 size: 数组有多大 count: 数组中当前存储了多少元素 顺序表三部分&#xff1a; 一段连续的存储区&#xff1a;顺序表存储元素的地方整型…

【Linux】Linux安装Java环境(OracleJDK)

文章目录 前言第一步&#xff0c;到官网下载jdk1.8第二步&#xff0c;下载下来上传到/opt目录下&#xff0c;并且解压第三步&#xff0c;解压之后配置环境变量&#xff1a;第四步&#xff0c;刷新配置文件第五步&#xff0c;查看版本 前言 linux环境为CentOS7.8 版本。 上期跟…

shell教程

面试题&#xff1a; 1.Shell中单引号和双引号区别 1)单引号不取变量值 2)双引号取变量值 3)反引号&#xff0c;执行引号中命令 4)双引号内部嵌套单引号&#xff0c;取出变量值 5)单引号内部嵌套双引号&#xff0c;不取出变量值 一、shell脚本 1.shell脚本概…

ChatGLM的搭建过程

本次搭建的是清华大学开源的ChatGLM。源码地址。模型地址。 1、开启BBR加速 如何开启BBR加速可以去看我的这篇文章&#xff0c;Linux开启内核BBR加速。 2、拉取ChatGLM源码和ChatGLM模型 点击这里跳转到源码处。 点击这里跳转到模型下载处。 我这里在下载之前创建了一个目…

与ChatGPT的一次技术对话

文章目录 前言 - 向引领前沿技术的伟大工作者致敬提问&#xff1a;请给我推荐几个最先进的目标检测AI模型提问&#xff1a;YOLOv4是什么原理&#xff0c;有什么创新提问&#xff1a;请问你知道yolov5吗提问&#xff1a; 那yolov5又有什么创新呢提问&#xff1a;你刚刚的回答正确…

SwiftUI 4.0 新 LabeledContent 视图帮您解决所有对齐烦恼

概览 在用 SwiftUI Form 设计 App 界面时&#xff0c;最头疼的就是内部视图对齐的问题了。好不容易适配了 iOS 中的布局&#xff0c;到了 iPadOS 或 MacOS 上却变得一团糟。 有没有一劳永逸&#xff0c;简单方便的办法呢&#xff1f; 如上图所示&#xff1a;我们利用 SwiftUI…

3. SQL底层执行原理详解

一条SQL在MySQL中是如何执行的 1. MySQL的内部组件结构1.1 Server层1.2 Store层 2. 连接器3. 分析器3.1 词法分析器原理 4. 优化器5. 执行器6. bin-log归档 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 …

基于ASP.NET的Web应用系统架构探讨

摘要&#xff1a;提出了一种基于ASP&#xff0e;NET开发方式的四层架构的Web应用系统构造思想。其基本内容是&#xff1a;将面向对象的UML建模与Web应用系统开发相结合&#xff0c;将整个系统分成适合ASP.NET开发方式的应用表示层、业务逻辑层、数据访问层和数据存储层四层结构…

[CF复盘] Codeforces Round 871 (Div. 4)20230506

[CF复盘] Codeforces Round 871 Div. 4 20230506 总结A. Love Story1. 题目描述2. 思路分析3. 代码实现 B. Blank Space1. 题目描述2. 思路分析3. 代码实现 C. Mr. Perfectly Fine1. 题目描述2. 思路分析3. 代码实现 D. Gold Rush1. 题目描述2. 思路分析3. 代码实现 E. The La…

UNeXt:基于 MLP 的快速医学图像分割网络

UNeXt是约翰霍普金斯大学在2022年发布的论文。它在早期阶段使用卷积&#xff0c;在潜在空间阶段使用 MLP。通过一个标记化的 MLP 块来标记和投影卷积特征&#xff0c;并使用 MLP 对表示进行建模。对输入通道进行移位&#xff0c;可以专注于学习局部依赖性。 UNeXt 基本架构 U…

考研数学高数1-1综合测试-函数及其性质

今天听完强化课之后去做学习包的题&#xff0c;发现没带平板&#xff0c;如果写到纸上&#xff0c;塞到书里又不知道去哪了&#xff0c;所以索性就拿Latex写了&#xff0c;虽然有一点麻烦&#xff0c;但是好在数量不多&#xff0c;就这么写吧。 都是我自己写的过程&#xff0c;…

第三十八章 Unity GUI系统(下)

上一章节我们将了UI的锚点&#xff0c;关于锚点我们只讲了一半&#xff0c;因为锚点并不只是一个点&#xff0c;它还可以是一个矩形。 我们可以将锚点拆开&#xff08;鼠标选中后拖动&#xff09;&#xff0c;也就是将“四瓣雪花”拆成“四瓣”。那么此时锚点就成为一个矩形。我…

00-docker篇: linux系统安装docker操作 (最实用的操作)

目录 1. docker 简介 -> 简易理解: -> docker是否有自己仓库呢 -> docker 是靠什么运行呢 -> 简单说点docker优点 2. linux安装docker ps: 如果是新服务器 请直接看2. 3 -> 2.1: 查看内核版本: -> 2.2 如果有残留docker, 删除指令 -> 2.3 yu…

2023年全国职业院校技能大赛网络建设与运维-网络运维部分

全国职业院校技能大赛 网络建设与运维 五、网络运维 某单位网络拓扑架构如下&#xff0c;交换机连接两台服务器&#xff0c;其中Server1服务器是数字取证服务器&#xff0c;Server2服务器是应急响应服务器&#xff0c;通过交换设备相连&#xff0c;通过路由设备连接到安全设…

Portraiture4最新版滤镜P图一键磨皮插件

今天coco玛奇朵给大家带来了一款ps磨皮插件&#xff0c;超级简单好用。Portraiture 滤镜是一款 Photoshop&#xff0c;Lightroom 和 Aperture 插件&#xff0c;DobeLighttroom 的 Portraiture 消除了选择性掩蔽和逐像素处理的繁琐的手工劳动&#xff0c;以帮助您在肖像修整方面…

如何使用 ChatGPT 来快速编写产品需求文档(PRD)

PRD 生成 ChatGPT 即了解具体的编程知识&#xff0c;也了解编程之前的需求设计过程。因此产品经理也可以使用 ChatGPT 来快速编写PRD(产品需求文档, production requirement documentation)。 根据需求编写 PRD 首先&#xff0c;我们可以尝试把需求交给 ChatGPT&#xff0c;…

模型如何压缩?使用轻量化的模型压缩技术剪枝(pruning)

深度学习模型参数太多&#xff0c;本地服务器部署没有问题&#xff0c;但是如果部署到移动端、边缘端&#xff0c;像手机、树莓派等&#xff0c;它们的性能不能满足&#xff0c;所以我们要压缩模型大小&#xff0c;让他们可以部署到边缘端 模型压缩&#xff1a;使用轻量化的模型…