ThreadLocal概述、解决SimpleDateFormat出现的异常、内存泄漏、弱引用、remove方法

①. ThreadLocal简介

①. ThreadLocal是什么

①. ThreadLocal本地线程变量,线程自带的变量副本(实现了每一个线程副本都有一个专属的本地变量,主要解决的就是让每一个线程绑定自己的值,自己用自己的,不跟别人争抢。通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全的问题)

②. synchronized或者lock,有个管理员,好比,现在大家签到,多个同学(线程),但是只有一只笔,只能同一个时间,只有一个线程(同学)签到,加锁(同步机制是以时间换空间,执行时间不一样,类似于排队)

③. ThreadLocal,人人有份,每个同学手上都有一支笔,自己用自己的,不用再加锁来维持秩序(同步机制是以空间换时间,为每一个线程都提供了一份变量的副本,从而实现同时访问,互不干扰同时访问,肯定效率高啊)

②. api介绍

  • ①. protected T initialValue​():initialValue():返回此线程局部变量的当前线程的"初始值"
  • (对于initialValue()较为老旧,jdk1.8又加入了withInitial()方法)

  • ②. static <S> ThreadLocal<S> withInitial​(Supplier<? extends S> supplier):创建线程局部变量

  • ③. T get​():返回当前线程的此线程局部变量的副本中的值

  • ④. void set​(T value):将当前线程的此线程局部变量的副本设置为指定的值

  • ⑤. void remove​():删除此线程局部变量的当前线程的值

③. 永远的helloword

/**** 看每个销售员可以出售多少套房子*/
class House{/**initialValue():返回此线程局部变量的当前线程的"初始值"对于initialValue()较为老旧,jdk1.8又加入了withInitial()方法ThreadLocal<Integer>threadLocal=new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return 0;}};*///public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)//withInitial(Supplier<? extends S> supplier):创建线程局部变量//ThreadLocal本地线程变量,线程自带的变量副本ThreadLocal<Integer>threadLocal=ThreadLocal.withInitial(()->0);public void saleHouse(){//T get():返回当前线程的此线程局部变量的副本中的值。Integer value = threadLocal.get();value++;//void set(T value):将当前线程的此线程局部变量的副本设置为指定的值。threadLocal.set(value);}
}
public class ThreadLocalDemo {public static void main(String[] args) {House house = new House();new Thread(()->{try{for (int i = 1; i <=3; i++) {house.saleHouse();}System.out.println(Thread.currentThread().getName()+"\t"+"卖出:"+house.threadLocal.get());}catch (Exception e){e.getStackTrace();}finally {//void remove():删除此线程局部变量的当前线程的值//在阿里巴巴手册中有说明,尽量在代理中使用try-finally块进行回收house.threadLocal.remove();//下面获取到的值是线程的初始值0System.out.println("**********"+house.threadLocal.get());}},"t1").start();new Thread(()->{try{for (int i = 1; i <=5; i++) {house.saleHouse();}System.out.println(Thread.currentThread().getName()+"\t"+"卖出:"+house.threadLocal.get());}catch (Exception e){e.getStackTrace();}finally {house.threadLocal.remove();}},"t2").start();new Thread(()->{try{for (int i = 1; i <=8; i++) {house.saleHouse();}System.out.println(Thread.currentThread().getName()+"\t"+"卖出:"+house.threadLocal.get());}catch (Exception e){e.getStackTrace();}finally {house.threadLocal.remove();}},"t3").start();System.out.println(Thread.currentThread().getName()+"\t"+"卖出了:"+house.threadLocal.get());}
}
/*** t1    卖出:3* t2    卖出:5* **********0* main    卖出了:0* t3    卖出:8* */

④. 通过上面代码总结

  • ①. 因为每个Thread内有自己的实例副本且该副本只由当前线程自己使用

  • ②. 既然其他Thread不可访问,那就不存在多线程共享的问题

  • ③. 统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的

  • ④. 加入synchronized或者lock控制线程的访问顺序,而ThreadLocal人手一份,大家各自安好,没必要抢夺

②. 从阿里ThreadLocal规范开始

  • ①. 公司业务:在对一些业务日志写入数据库的时候,日期调用了sdf的静态,导致了会报错或者日期乱了(生产故障)
  • 编辑

 

①. 非线程安全的SimpleDateFormat

  • ①. 写时间工具类,一般写成静态的成员变量,不知,此种写法的多线程下的危险性!

  • ②. 代码展示

public class DateUtils
{
    public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * 模拟并发环境下使用SimpleDateFormat的parse方法将字符串转换成Date对象
     * @param stringDate
     * @return
     * @throws Exception
     */
    public static Date parseDate(String stringDate)throws Exception
    {
        return sdf.parse(stringDate);
    }
    
    public static void main(String[] args) throws Exception
    {
        for (int i = 1; i <=30; i++) {
            new Thread(() -> {
                try {
                    System.out.println(DateUtils.parseDate("2020-11-11 11:11:11"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
 

③. bug
 

④. 源码分析结论(了解)

SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和这个SimpleDateFormat相关的日期信息,例如sdf.parse(dateStr),sdf.format(date) 诸如此类的方法参数传入的日期相关String,Date等等, 都是交由Calendar引用来储存的.这样就会导致一个问题如果你的SimpleDateFormat是个static的, 那么多个thread 之间就会共享这个SimpleDateFormat, 同时也是共享这个Calendar引用

 

②. 将SimpleDateFormat定义成局部变量(方案一)

  • 缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收
public class DateUtils
{public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");/*** 模拟并发环境下使用SimpleDateFormat的parse方法将字符串转换成Date对象* @param stringDate* @return* @throws Exception*/public static Date parseDate(String stringDate)throws Exception{return sdf.parse(stringDate);}public static void main(String[] args) throws Exception{for (int i = 1; i <=30; i++) {new Thread(() -> {try {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(sdf.parse("2020-11-11 11:11:11"));sdf = null;} catch (Exception e) {e.printStackTrace();}},String.valueOf(i)).start();}}

③. ThreadLocal 解决日期格式乱码问题

/**
 * 在对一些业务日志写入数据库的时候,日期调用了sdf的静态,导致了会报错或者日期乱了
 * */
public class ThreadLocalDataUtils {

    public static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     解决方案一:加入synchronized,用时间换空间,效率低
    */
    /**
     如果不加会导致线程安全问题,SimpleDateFormat类内部有一个Calendar对象引用,
     SimpleDateFormat相关的日期信息,例如sdf.parse(dateStr),sdf.format(date)
     诸如此类的方法参数传入的日期相关String,Date等等, 都是交由Calendar引用来储存的.
     这样就会导致一个问题如果你的SimpleDateFormat是个static的,那么多个thread之间
     就会共享这个SimpleDateFormat,同时也是共享这个Calendar引用(相当于买票案列)
    */
    //public static synchronized Date parse(String stringDate) throws ParseException {
    public static  Date parse(String stringDate) throws ParseException {
        System.out.println(sdf.parse(stringDate));
        return sdf.parse(stringDate);
    }

    /***
     * 解决方案二:使用ThreadLocal,用空间换时间,效率高
     * ThreadLocal中变量副本会人手一份,每次使用完了threadLocal后都要将资源进行释放的处理
     */
    public static final ThreadLocal<SimpleDateFormat>sdfThreadLocal=
            ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    public static  Date parseByThreadLocal(String stringDate) throws ParseException {
        return sdfThreadLocal.get().parse(stringDate);
    }
    //3 DateTimeFormatter 代替 SimpleDateFormat
    public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String formatForDateTime(LocalDateTime localDateTime) {
        return DATE_TIME_FORMAT.format(localDateTime);
    }

    public static LocalDateTime parseForDateTime(String dateString) {
        return LocalDateTime.parse(dateString,DATE_TIME_FORMAT);
    }

    public static void main(String[] args) throws Exception{
        for (int i = 1; i <=3; i++) {
            new Thread(()->{
                try {
                   //ThreadLocalDataUtils.parse("2021-03-30 11:20:30");
                   //System.out.println(ThreadLocalDataUtils.parseByThreadLocal("2021-03-30 11:20:30"));
                   System.out.println(ThreadLocalDataUtils.parseForDateTime("2021-03-30 11:20:30"));
                   // System.out.println(ThreadLocalDataUtils.formatForDateTime(LocalDateTime.now()));
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    ThreadLocalDataUtils.sdfThreadLocal.remove();
                }
            },String.valueOf(i)).start();
        }
    }
}
 

④. 阿里规范怎么说的?

 

③. ThreadLocal源码分析

①. Thread|ThreadLocal|ThreadLocalMap关系

  • ①. Thread和ThreadLocal
  • ②. ThreadLocal和ThreadLocalMap
  • ③. All三者总概括

    1.Thread类中有一个ThreadLocal.ThreadLocalMap threadLocals = null的变量,这个ThreadLocal相当于是Thread类和ThreadLocalMap的桥梁,在ThreadLocal中有静态内部类ThreadLocalMap,ThreadLocalMap中有Entry数组

    2.当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放        

    3.t.threadLocals = new ThreadLocalMap(this, firstValue) 如下这行代码,可以知道每个线程都会创建一个ThreadLocalMap对象,每个线程都有自己的变量副本

    //核心代码说明
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    void createMap(Thread t, T firstValue) {
       t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
     

    ②. set方法详解

    • ​①. 首先获取当前线程,并根据当前线程获取一个Map

    • ②. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)

    • ③. 如果Map为空,则给该线程创建 Map,并设置初始值

     /**
         * 设置当前线程对应的ThreadLocal的值
         *
         * @param value 将要保存在当前线程对应的ThreadLocal的值
         */
        public void set(T value) {
            // 获取当前线程对象
            Thread t = Thread.currentThread();
            // 获取此线程对象中维护的ThreadLocalMap对象
            ThreadLocalMap map = getMap(t);
            // 判断map是否存在
            if (map != null)
                // 存在则调用map.set设置此实体entry
                map.set(this, value);
            else
                // 1)当前线程Thread 不存在ThreadLocalMap对象
                // 2)则调用createMap进行ThreadLocalMap对象的初始化
                // 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
                createMap(t, value);
        }

     /**
         * 获取当前线程Thread对应维护的ThreadLocalMap 
         * 
         * @param  t the current thread 当前线程
         * @return the map 对应维护的ThreadLocalMap 
         */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
        /**
         *创建当前线程Thread对应维护的ThreadLocalMap 
         *
         * @param t 当前线程
         * @param firstValue 存放到map中第一个entry的值
         */
        void createMap(Thread t, T firstValue) {
            //这里的this是调用此方法的threadLocal
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
        
         /*
          * firstKey : 本ThreadLocal实例(this)
          * firstValue : 要保存的线程本地变量
          */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                //初始化table
                table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
                //计算索引(重点代码)
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                //设置值
                table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
                size = 1;
                //设置阈值
                setThreshold(INITIAL_CAPACITY);
            }
     

    ③. get方法详解

    1. 先获取当前线程的ThreadLocalMap变量,如果存在则返回值,不存在则创建并返回初始值
      /*** 返回当前线程中保存ThreadLocal的值* 如果当前线程没有此ThreadLocal变量,* 则它会通过调用{@link #initialValue} 方法进行初始化值** @return 返回当前线程对应此ThreadLocal的值*/public T get() {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果此map存在if (map != null) {// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体eThreadLocalMap.Entry e = map.getEntry(this);// 对e进行判空 if (e != null) {@SuppressWarnings("unchecked")// 获取存储实体 e 对应的 value值// 即为我们想要的当前线程对应此ThreadLocal的值T result = (T)e.value;return result;}}/*初始化 : 有两种情况有执行当前代码第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry*/return setInitialValue();}/*** 初始化** @return the initial value 初始化后的值*/private T setInitialValue() {// 调用initialValue获取初始化的值// 此方法可以被子类重写, 如果不重写默认返回nullT value = initialValue();// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 判断map是否存在if (map != null)// 存在则调用map.set设置此实体entrymap.set(this, value);else// 1)当前线程Thread 不存在ThreadLocalMap对象// 2)则调用createMap进行ThreadLocalMap对象的初始化// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中createMap(t, value);// 返回设置的值valuereturn value;}

     

    ④. remove方法详解

    • ①. 首先获取当前线程,并根据当前线程获取一个Map

    • ​②. 如果获取的Map不为空,则移除当前ThreadLocal对象对应的entry

         /**
         * 删除当前线程中保存的ThreadLocal对应的实体entry
         */
         public void remove() {
            // 获取当前线程对象中维护的ThreadLocalMap对象
             ThreadLocalMap m = getMap(Thread.currentThread());
            // 如果此map存在
             if (m != null)
                // 存在则调用map.remove
                // 以当前ThreadLocal为key删除对应的实体entry
                 m.remove(this);
         }
     

    ④. ThreadLocal内存泄漏问题

    ①. 为什么源代码用弱引用?

    • ①. 当function01方法执行完毕后,栈帧销毁强引用 tl 也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象

    • ②. 若这个key引用是强引用,就会导致key指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏

    • ③. 若这个key引用是弱引用就大概率会减少内存泄漏的问题(还有一个key为null的雷)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null

    ②. key为null的entry,原理解析

    • ①. ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些key为null的Entry的value就会一直存在一条强引用链

    • ②. 虽然弱引用,保证了key指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时才会去回收整个entry、value

    • ③. 因此弱引用不能100%保证内存不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug

    • ④. 如果当前thread运行结束,threadLocal,threadLocalMap, Entry没有引用链可达,在垃圾回收的时候都会被系统进行回收

    • ⑤. 但在实际使用中我们有时候会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们小心
    • ⑥. 出现内存泄漏的真实原因 (1). 没有手动删除这个Entry (2). CurrentThread依然运行

    ③. set、get方法会去检查所有键为null的Entry对象

    • ①. set( )

     

     ②. get( )


     ③. remove( )

    ④. 结论(在finally后面调用remove方法)

    ⑤. ThreadLocal小总结

    • ①. ThreadLocal本地线程变量,以空间换时间,线程自带的变量副本,人手一份,避免了线程安全问题

    • ②. 每个线程持有一个只属于自己的专属Map并维护了Thread Local对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题

    • ③. ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题

    • ④. 都会通过expungeStaleEntry,cleanSomeSlots, replace StaleEntry这三个方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏,属于安全加固的方法

    • ⑤. 用完之后一定要remove操作

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

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

      相关文章

      总结8..

      #include <stdio.h> // 定义结构体表示二叉树节点&#xff0c;包含左右子节点编号 struct node { int l; int r; } tree[100000]; // 全局变量记录二叉树最大深度&#xff0c;初始为0 int ans 0; // 深度优先搜索函数 // pos: 当前节点在数组中的位置&#xff0c…

      科普篇 | “机架、塔式、刀片”三类服务器对比

      一、引言 在互联网的世界里&#xff0c;服务器就像是默默运转的超级大脑&#xff0c;支撑着我们日常使用的各种网络服务。今天&#xff0c;咱们来聊聊服务器家族中的三位 “明星成员”&#xff1a;机架式服务器、塔式服务器和刀片式服务器。如果把互联网比作一座庞大的城市&…

      Day25-【13003】短文,什么是算法?如何衡量时间复杂度?什么是最优,平均时间复杂度?

      文章目录 第二节概览什么是算法&#xff1f;算法的5个特性&#xff1f; 算法如何评估&#xff1f;时间指标如何衡量&#xff1f;算法的复杂度如何度量&#xff1f;算法开销上限和下限如何表示&#xff1f;什么是常数复杂度&#xff1f;线性操作&#xff1f;对数复杂度-线性对数…

      python基础语法(3) -------- 学习笔记分享

      目录: 1. 函数 1.1 语法格式 1.2 函数参数 1.3 函数返回值 1.4 变量的作用域 1.5 函数的执行过程 1.6 函数的链式调用 1.7 函数的嵌套调用 1.8 函数递归 1.9 参数默认值 1.10 函数的关键字传参 2. 列表和元组 2.1 列表和元组是啥 2.2 创建列表 2.3 访问下标 2.…

      磐维数据库PanWeiDB2.0日常维护

      磐维数据库简介 “中国移动磐维数据库”&#xff08;ChinaMobileDB&#xff09;&#xff0c;简称“磐维数据库”&#xff08;PanWeiDB&#xff09;。是中国移动信息技术中心首个基于中国本土开源数据库打造的面向ICT基础设施的自研数据库产品。 其产品内核能力基于华为 OpenG…

      Linux:文件与fd(未被打开的文件)

      hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《Linux&#xff1a;文件与fd&#xff08;未被打开的文件&#xff09;》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xf…

      自动驾驶中的多传感器时间同步

      目录 前言 1.多传感器时间特点 2.统一时钟源 2.1 时钟源 2.2 PPSGPRMC 2.3 PTP 2.4 全域架构时间同步方案 3.时间戳误差 3.1 硬件同步 3.2 软件同步 3.2.3 其他方式 ① ROS 中的 message_filters 包 ② 双端队列 std::deque 参考&#xff1a; 前言 对多传感器数据…

      U-Net - U型网络:用于图像分割的卷积神经网络

      U-Net是一种专为图像分割任务设计的卷积神经网络&#xff08;CNN&#xff09;&#xff0c;最初由Olaf Ronneberger等人于2015年提出。它被广泛应用于医学影像分析、遥感图像分割、自动驾驶和其他许多需要对图像进行像素级分类的任务中。U-Net具有强大的特征提取和恢复能力&…

      关于使用PHP时WordPress排错——“这意味着您在wp-config.php文件中指定的用户名和密码信息不正确”的解决办法

      本来是看到一位好友的自己建站&#xff0c;所以突发奇想&#xff0c;在本地装个WordPress玩玩吧&#xff0c;就尝试着装了一下&#xff0c;因为之前电脑上就有MySQL&#xff0c;所以在自己使用PHP建立MySQL时报错了。 最开始是我的php启动mysql时有问题&#xff0c;也就是启动过…

      写一个存储“网站”的网站前的分析

      要创建一个能够存储自己网站内容的“网站”,通常意味着你希望有一个可以存储网站数据、文件、内容等信息的系统。为了实现这一目标,可以考虑构建一个内容管理系统(CMS),这个系统能够帮助你存储和管理网站上的内容。 图片仅供参考 以下是如何实现一个可以存储自己网站内容…

      [STM32 标准库]定时器输出PWM配置流程 PWM模式解析

      前言&#xff1a; 本文内容基本来自江协&#xff0c;整理起来方便日后开发使用。MCU&#xff1a;STM32F103C8T6。 一、配置流程 1、开启GPIO&#xff0c;TIM的时钟 /*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockC…

      KIMI K1.5:用大语言模型扩展强化学习(论文翻译)

      文章目录 KIMI K1.5技术报告摘要 1. 引言2. 方法&#xff1a;基于大语言模型的强化学习2.1 强化学习提示集整理2.2 长思维链监督微调2.3 强化学习2.3.1 问题设定2.3.2 策略优化2.3.3 长度惩罚2.3.4 采样策略2.3.5 训练方法的更多细节 2.4 长到短&#xff1a;短思维链模型的上下…

      python:taichi 高性能可视化 Demo 展览

      安装 pip install taichi taichi-1.7.3-cp39-cp39-win_amd64.whl (83.1 MB) 运行 cmd where ti D:\Python39\Scripts\ti.exe #-- taichi 高性能可视化 Demo 展览 ti gallery [Taichi] version 1.7.3, llvm 15.0.1, commit 5ec301be, win, python 3.9.13********************…

      电脑无法开机,重装系统后没有驱动且驱动安装失败

      电脑无法开机&#xff0c;重装系统后没有驱动且驱动安装失败 前几天电脑突然坏了&#xff0c;电脑卡住后&#xff0c;强制关机&#xff0c;再开机后开机马上就关机。尝试无数次开机后失败&#xff0c;进入BIOS界面&#xff0c;发现已经没有Windows系统了。重新安装系统后&…

      2024年度总结——理想的风,吹进现实

      2024年悄然过去&#xff0c;留下了太多美好的回忆&#xff0c;不得不感慨一声时间过得真快啊&#xff01;旧年风雪尽&#xff0c;新岁星河明。写下这篇博客&#xff0c;记录我独一无二的2024年。这一年&#xff0c;理想的风终于吹进现实&#xff01; 如果用一句话总结这一年&am…

      Juc22_什么是中断、interrupt、isInterrupted、interrupted方法源码解析、如何使用中断标识停止线程

      目录 ①. 什么是中断 ②. 源码解读(中断的相关API) ③. 如何使用中断标识停止线程 ①. 什么是中断 ①. 一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止,所以,Thread.stop、Thread.suspend、Thread. resume都已经被废弃了 ②. 在Java中没有办法立即停止…

      《用DOTS解决实际需求》集锦

      去年作者发布了一篇《DOTS-ECS系列课程》&#xff0c;深受同学们的好评&#xff01;前期课程是基于0.51版本录制的&#xff0c;DOTS升级至1.0版本后&#xff0c;同学们纷纷希望能使用DOTS 1.0版本录制实战课程。 今年作者带着DOTS 1.0版本的实战课程回来啦&#xff01;&#x…

      HTTP 配置与应用(不同网段)

      想做一个自己学习的有关的csdn账号&#xff0c;努力奋斗......会更新我计算机网络实验课程的所有内容&#xff0c;还有其他的学习知识^_^&#xff0c;为自己巩固一下所学知识&#xff0c;下次更新校园网设计。 我是一个萌新小白&#xff0c;有误地方请大家指正&#xff0c;谢谢…

      Crawl4AI 人工智能自动采集数据

      文章目录 1 使用 Crawl 的步骤2 AI 智能体应用实例3 结语 Crawl是一款免费的开源工具&#xff0c;利用AI技术简化网络爬取和数据提取&#xff0c;提高信息收集与分析的效率。它智能识别网页内容&#xff0c;并将数据转换为易于处理的格式&#xff0c;功能全面且操作简便。 定位…

      KVM/ARM——基于ARM虚拟化扩展的VMM

      1. 前言 ARM架构为了支持虚拟化做了些扩展&#xff0c;称为虚拟化扩展(Virtualization Extensions)。原先为VT-x创建的KVM(Linux-based Kernel Virtual Machine)适配了ARM体系结构&#xff0c;引入了KVM/ARM (the Linux ARM hypervisor)。KVM/ARM没有在hypervisor中引入复杂的…