ThreadLocal详解

2023-11-18

如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:瞳孔空间

一:基本介绍

ThreadLocal类能提供线程内部的局部变量。这种变量在多线程环境下访问时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。

归纳要点,即:

  • 线程并发:在多线程并发的场景下
  • 传递数据:我们可以通过ThreadLocal在同一线程的不同组件中传递公共变量
  • 线程隔离:每个线程的变量都是独立的,不会互相影响

二:基本使用

ThreadLocal有以下四个常用方法:

方法声明 描述
ThreadLocal() 创建ThreadLocal对象
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove 移除当前线程绑定的局部变量

现在有如下使用场景:

  • 线程A:设置变量1,获取变量1
  • 线程B:设置变量2,获取变量2

代码如下:

/**
 * @author eyes
 * @date 2023/1/21 9:15
 */
@Data
public class Demo {
  // 变量
  private String content;

  public static void main(String[] args) {
    Demo demo = new Demo();
    for (int i = 0; i < 5; i++) {
      new Thread(() -> {
        demo.setContent(Thread.currentThread().getName());
        System.out.println("");  // 打印这个是为了让线程在setContent之后不立即执行下面的输出,让访问错乱的效果更明显
        System.out.println(Thread.currentThread().getName() + "----->" + demo.getContent());
      }, "线程" + i).start();
    }
  }
}

在这里插入图片描述
可见如果不将线程隔离,那么多线程并发场景下就会导致错乱,为此可以使用ThreadLocal进行改进:

/**
 * @author eyes
 * @date 2023/1/21 9:15
 */
public class Demo {
  ThreadLocal<String> tl = new ThreadLocal<>();

  private String getContent() {
    return tl.get();
  }

  private void setContent(String content) {
    tl.set(content);
  }

  public static void main(String[] args) {
    Demo demo = new Demo();
    for (int i = 0; i < 5; i++) {
      new Thread(() -> {
        demo.setContent(Thread.currentThread().getName());
        System.out.println("");  // 打印这个是为了让线程在setContent之后不立即执行下面的输出,让访问错乱的效果更明显
        System.out.println(Thread.currentThread().getName() + "----->" + demo.getContent());
      }, "线程" + i).start();
    }
  }
}

在这里插入图片描述

三:源码解析

3.1:内部结构

如果我们不去看源代码的话,可能会猜测ThreadLocal是这样子设计的:每个ThreadLocal都创建一个Map,然后用线程作为Map的key,要存储的局部变量作为value,这样就能达到各个线程的局部变量隔离的效果。这是最简单的设计方法,JDK最早期的ThreadLocal确实是这样设计的,如下图:

在这里插入图片描述
但是,JDK后面优化了设计方案,在JDK8中 ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值object。具体的过程是这样的:

  • 每个Thread线程内部都有一个Map(ThreadLocalMap)
  • Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
  • Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
  • 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

在这里插入图片描述

两者对比可知,JDK8的设计方案有如下好处:

  • 在实际生产环境中,ThreadLocal数往往少于Thread数,因此该方案的每个ThreadLocalMap存储的Entry数量变少,减少了哈希冲突,效率更高。
  • 当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存的占用

3.2:核心方法源码

基于ThreadLocal的内部结构,我们继续分析它的核心方法源码,更深入的了解其操作原理。ThreadLocalMap是ThreadLocal的静态内部类,由于内容较多,因此ThreadLocalMap单独放到3.3中介绍。

除了构造方法外,ThreadLocal对外暴露的方法有以下4个:

方法声明 描述
protected initialValue() 返回当前线程局部变量的初始值
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove 移除当前线程绑定的局部变量

3.2.1:set

    /**
     * 设置当前线程对应的ThreadLocal的值
     *
     * @param 将要保存在当前线程对应的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 {
        	// 当前线程Thread不存在ThreadLocalMap对象则创建该对象
        	// 并将t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
        }
    }

    /**
     * 获取当前线程Thread对应维护的ThreadLocalMap
     *
     * @param t 当前线程
     * @return 对应维护的ThreadLocalMap
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }


    /**
     * 创建当前线程Thread对应维护的ThreadLocalMap
     *
     * @param t 当前线程
     * @param firstValue 存放到map中的第一个entry的值
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

3.2.2:get


    /**
     * 返回当前线程中保存ThreadLocal的值
     * 如果当前线程没有此ThreadLocal变量,则调用initialvalue方法进行初始化值
     * 
     * @return 当前线程对应此ThreadLocal的值
     */
    public T get() {
    	// 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null) {
        	// 以当前的ThreadLocal为key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取存储实体e对应的value值
                // 即为我们想要的当前线程对应此ThreadLocal的值
                T result = (T)e.value;
                return result;
            }
        }
        // 调用initialvalue方法进行初始化值
        // 有两种情况会执行当前代码
        // 1. map不存在,表示此线程没有维护的ThreadLocalMap对象
        // 2. map存在,但是没有与当前ThreadLocal关联的entry
        return setInitialValue();
    }


    /**
     * set()方法的变种,用以构建初始化值 
     * 当set()方法被重写时用以替代原set()方法
     *
     * @return 初始化值
     */
    private T setInitialValue() {
    	// 调用initialValue获取初始化的值
    	// 此方法可以被子类重写,如果不重写则默认返回null
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	// map存在则调用map.set()设置此实体类
            map.set(this, value);
        } else {
        	// map不存在则调用createMap进行ThreadLocalMap对象的初始化
        	// 并将t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

3.2.3:remove


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

3.2.4:initialValue


    /**
     * 返回当前线程对应的ThreadLocal的初始值
     * 当线程没有先调用set方法就调用get方法时,此方法才会执行
     * 
     * 这个方法仅仅简单返回null,如果程序员想ThreadLocal线程局部变量有一个
     * 除null以外的初始值,必须通过子类继承的方式去重写此方法,通常可以用匿名内部类实现
     * 该方法是protected,显然是为了让子类覆盖而设计的
     * 
     * @return 当前ThreadLocal的初始值
     */
    protected T initialValue() {
        return null;
    }

匿名内部类重写initialValue方法:

  ThreadLocal tl = new ThreadLocal() {
    @Override
    protected String initialValue() {
      return "瞳孔";
    }
  };

除此之外,ThreadLocal还提供了一个便捷的静态方法:

ThreadLocal<String> tl = ThreadLocal.withInitial(() -> "瞳孔");

这样的话就可以优雅地设置初始值了:
在这里插入图片描述

3.3:ThreadLocalMap

ThreadLocalMap是ThreadLocal的静态内部类,虽然它叫map,但并没有实现Map接口,它用独立的方式实现了Map的功能,其内部的Entry也是独立实现的。
在这里插入图片描述

3.3.1:成员变量


        /**
         * 初始容量 -- 必须是2的整次幂
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * 存放数据的table, 大小会根据需要调整
         * 数组长度必须是2的整次幂
         */
        private Entry[] table;

        /**
         * 数组里entrys的个数,可以用于判断table当前使用量是否超过阈值
         */
        private int size = 0;

        /**
         * 进行扩容的阈值,表使用量大于它的时候进行扩容
         * 默认为0
         */
        private int threshold;

3.3.2:存储结构 - Entry

ThreadLocalMap用Entry来保存K-V结构数据。不过Entry的key只能是ThreadLocal对象,这点在构造方法中已经限定死了。

另外,Entry继承WeakReference,也就是key(Threadlocal)是弱引用,其目的是将Threadlocal对象的生命周期和线程生命周期解绑。


        /**
         * Entry继承WeakReference,并且用ThreadLocal作为key
         * 如果key为null(entry.get() == null),意味着key不再被引用,此时entry可以从table中清除
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

3.3.3:内存泄漏问题

不了解JVM的话建议先看看JVM垃圾回收再看,推荐这篇:JVM详解——垃圾回收

内存泄漏相关概念:

  • Memory overflow:内存溢出,没有足够的内存提供申请者使用。
  • Memory leak:内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。

Java中的引用有4种类型:强引用、软引用、弱引用和虚引用。当前这个问题只涉及强引用和弱引用。

  • 强引用(StrongReference):最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”。内存不足时,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
  • 软引用(SoftReference):软引用是用来描述一些有用但并不是必需的对象。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存,比如网页缓存、图片缓存等。
  • 弱引用(WeakReference):用来描述非必需对象,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。弱引用与软引用的区别在于,只具有弱引用的对象拥有更短暂的生命周期。软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
  • 虚引用(PhantomReference):和软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动。

有时候使用ThreadLocal的过程中会发现有内存泄漏的情况发生,就猜测这个内存泄漏跟Entry中使用了弱引用的key有关系。这个理解其实是不对的。先看下面这张图,假设Entry用的是强引用:

在这里插入图片描述
假设在业务代码中使用完ThreadLocal,ThreadLocal Ref被回收了。但是因为ThreadLocalMap的Entry强引用了ThreadLocal,造成ThreadLocal无法被回收。在没有手动删除这个Entry以及CurrentThread依然运行的前提下,
始终有强引用链 threadRef->currentThread->threadLocalMap->entry,Entry就不会被回收(Entry中包括了ThreadLocal实例和value),导致Entry内存泄漏。也就是说,ThreadLocalMap中的key使用了强引用,也是无法完全避免内存泄漏的。

下面是key使用弱引用的情况:

在这里插入图片描述

同样假设在业务代码中使用完ThreadLocal,ThreadLocal Ref被回收了。由于ThreadLocalMap只持有ThreadLocal的弱引用,没有任何强引用指向Threadlocal实例,所以Threadlocal就可以顺利被gc回收,此时Entry中的key=null。但是在没有手动删除这个Entry以及CurrentThread依然运行的前提下,也存在有强引用链 ThreadRef->currentThread->threadLocalMap->entry ->value,value不会被回收,而这块value永远不会被访问到了,导致value内存泄漏。也就是说,ThreadLocalMap中的key使用了弱引用,也有可能内存泄漏。

因此我们可以知道,无论ThreadLocalMap中的key使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。

要避免内存泄漏有两种方式:

  • 使用完ThreadLocal,调用其remove方法删除对应的Entry
  • 使用完ThreadLocal,当前Thread也随之运行结束

相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的。也就是说,只要记得在使用完ThreadLocal及时的调用remove,无论key是强引用还是弱引用都不会有问题。

那么为什么key要用弱引用呢?事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。这就意味着使用完ThreadLocal , CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set、get、remove中的任一方法的时候会被清除,从而避兔内存泄漏。

3.3.4:Hash冲突问题

和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。ThreadLocalMap解决Hash冲突的方式就是简单的步长加1,寻找下一个相邻的位置。

		private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 // 在这里调用nextIndex选择数据存入的位置
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ThreadLocal详解 的相关文章

  • (更好的方法)使用 Eclipse 和 XText 获取项目中的文件

    我正在编写一个 XText 编辑器 并进行一些语义突出显示 我正在解析的部分语言引用了文件 这些文件应该存在于项目中 我想根据这些文件是否位于正确的位置来突出显示 目前 我有一个非常丑陋的解决方案 但我确信有更好的方法 public voi
  • Java中如何合并两个数组?

    它不是连接而是合并两个数组 使它们成为名称值对的数组 firstarray a aa aaa secondarray b bb bbb result a b aa bb aaa bbb 最好的方法是什么 in Java public sta
  • 从SQLite列中获取所有数字字符串并进行总和计算

    我是 Android 和 SQLite 的新手 我在 SQLite 中有一个只有数字的 AMOUNT 列 我可以在 ListView 中显示它 但我无法找到任何我理解的方法来将它们全部添加并显示在 TextView 中 这是数据库助手 im
  • Java 7u51/7u55 带星号的清单变量

    我正在部署一个小程序 其中包含清单中的下一个变量 Manifest Version 2 0 Ant Version Apache Ant 1 8 2 Trusted Library true Permissions all permissi
  • Java中的字节和字符转换

    如果我将一个字符转换为byte然后回到char 那个角色神秘地消失了 变成了别的东西 这怎么可能 这是代码 char a line 1 byte b byte a line 2 char c char b line 3 System out
  • Java:等于和==

    让我们看看我们有 2 个对用户定义类实例的引用 即 Java 中的 a 和 b 会不会有一种情况 a b 但 a equals b 返回 false 当然 实施 equals 完全取决于班级 所以我可以写 class Foo public
  • 我从 String placeName = placeText.getText().toString(); 收到空指针异常

    您好 想从编辑文本中获取地名并在地图上标记 这是我的代码 其中出现空指针异常 请帮助我应该做什么以及哪里出错了 因为我从对话框中的编辑文本字段获取地名 View layout View inflate this R layout alert
  • 如何对 jar 文件资源使用 File.separator?

    我正在尝试读取位于 jar 文件中的属性文件 我想使用 File separator 因为应用程序将在多个平台上运行 我正在构建路径如下 jarFilePath jar file jarFile getAbsolutePath jarPro
  • Spark SQL 失败,因为“常量池已超过 JVM 限制 0xFFFF”

    我在 EMR 4 6 0 Spark 1 6 1 上运行此代码 val sqlContext SQLContext getOrCreate sc val inputRDD sqlContext read json input try inp
  • 加密 mongodb 中的密码字段

    我有以下代码 它插入userName and password进入数据库 但密码以纯文本格式存储 我的意思是 当我查看数据库时 我可以看到插入的密码 我想存储password in encrypted format MongoClient
  • 无法删除临时文件夹(有时)

    当我启动应用程序时 我创建一个临时文件夹 public static File createTempDir String name throws IOException File tempDir File createTempFile na
  • 如何组合 3 个或更多 CompletionStages?

    如果有 2 个 CompletionStages 我可以将它们与thenCombine method CompletionStage a aCompletionStage getA CompletionStage b bCompletion
  • Java:如果数组大小未知,如何初始化?

    我要求用户输入 1 到 100 之间的一些数字并将它们分配到一个数组中 数组大小未初始化 因为它取决于用户输入数字的次数 我应该如何分配数组长度 如果用户输入 5 6 7 8 9 5 个数字 则 int list becomes int l
  • 为什么 CompletableFuture 的 thenAccept() 不在主线程上运行

    我在 CompletableFuture 的 SupplyAsync 中处理长时间运行的操作 并将结果放入 thenAccept 中 有时 thenAccept 在主线程上执行 但有时它在工作线程上运行 但我只想在主线程上运行 thenAc
  • 为什么从类构造函数调用的方法应该是最终的? [复制]

    这个问题在这里已经有答案了 我是一名 Java 新手 我试图理解 Oracle 网站教程中的以下行 https docs oracle com javase tutorial java IandI final html https docs
  • 尝试通过 Java 8 中的 JDBC-ODBC 连接到 .accdb 文件时出现 ClassNotFoundException

    我正在 Eclipse EE IDE 中的 Java 项目中工作 我必须在其中查询 accdb文件 问题是当我尝试加载驱动程序然后连接到数据库时 它给了我一个异常错误 My code try String filePath myfilepa
  • Java 执行器和长寿命线程

    我继承了一些使用 Executors newFixedThreadPool 4 的代码运行 4 个长寿命线程来完成应用程序的所有工作 这是推荐的吗 我读过Java 并发实践 https rads stackoverflow com amzn
  • 隐藏 JTable 临时列

    我正在使用 JTable 显示数据库中的数据 现在我想通过 Jcombobox 过滤我的 jtable 我正在使用 Jcombo 框 其中包含 030 024 045 等值 这些值已在 jtable 中设置为列标题 当我单击组合时 选定的列
  • 为什么 OOP 中静态类的最佳实践有所不同?

    我目前正在阅读有关 Java 最佳实践的内容 我发现根据这本书 https rads stackoverflow com amzn click com 0321356683我们必须优先选择静态类而不是非静态类 我记得在 C 最佳实践中 我们
  • RetentionPolicy CLASS 与 RUNTIME

    两者之间有什么实际区别RetentionPolicy CLASS and RetentionPolicy RUNTIME 看起来两者都被记录到字节码中 并且无论如何都可以在运行时访问 无论如何 两者都可以在运行时访问 那不是那个javado

随机推荐

  • vue +C# mvc 坑

    1 把input标签放入到form表单中后 再将input中的值新增到数据库后 界面会刷新 解决 删除form 标签 原因待查
  • win11 安装opencv-python

    首先下载python3 7以上版本 installer版本 选择安装pip 添加环境 cmd 打开终端 pip install i https pypi tuna tsinghua edu cn simple opencv python p
  • List集合给另一个List集合赋值问题(即浅拷贝与深拷贝)

    问题 java中将一个list的内容复制给另一个list之后 去新的list进行操作的时候 原来的list也会发生变化 解决方案 直接采用 进行的复制属于浅层赋值 两者指向的使用一个地址 所以操作一个时另一个也会发生变化 如果不想出现这样的
  • 搜索插入位置 JavaScript

    在有序数组中查找 无则插入 元素 返回索引 题目详情 https leetcode cn com problems search insert position description 遍历数组查找 插入 查找比较简单 插入时会麻烦一些 如
  • Java中的String用法

    新手Java程序员了解String类型 1 String是什么数据类型 String在定义上是java lang包下的一个类 它不是基本的数据类型 String是不可变的 JVM使用字符串池来存储所有的字符串对象 2 创建String对象的
  • 阅读书源最新2020在线导入_最最最最最好用的小说神器,全网书源免费用!

    今天给大家分享的是小说软件 为了满足所有小伙伴的需求 今天安卓和苹果都安排上了 一款Android应用 一款iOS应用 两款应用都十分相似 都是可以自行添加书源的软件 几乎覆盖全网小说 阅读 Android 软件本身是没有任何资源的 如果不
  • elasticsearch心得体会

    1 聚合搜索的字段如果是求和的情况下 字段不能为string类型 那么排序时字段要求是不是也一样呢
  • Qt中带token的Http请求

    详细代码 get请求 QTimer timer timer setInterval 5000 设置超时时间 timer setSingleShot true 单次触发 if m pGetManager delete m pGetManage
  • gstreamer中tee如何实现动态增减支路(预览+截图+录像)

    系列文章目录 Gstreamer中获取帧数据的方式 gstreamer中如何使用probe 探针 获取帧数据 gstreamer拉流rtsp使用appsink获取帧数据 预览 截图 gstreamer中如何使用fakesink获取帧数据 预
  • Vue3/ Vue3 生命周期 钩子函数 总结 、Vue3 内 主要 钩子函数 、Vue2 和 Vue3 生命周期钩子函数对比

    一 Vue3 生命周期 钩子函数 总结 1 介绍 Vue组件简介 Vue是组件haul编程 从一个组件诞生到消亡 会经历很多过程 这些过程就叫做生命周期 例如 生命周期就是人出生到入土是一样的 有少年时期 青年时期 中年时期 老年时期 每个
  • Requests

    REQUESTS的基本操作 参考崔庆才爬虫 GET请求 import requests r requests get http www gdsgj com r encoding r apparent encoding 根据从返回内容中解析的
  • 构造函数不能被继承

    构造函数不同于其他类方法 因为他创建新的对象 而其他类方法只是被现有的对象调用 这是构造函数不被继承的原因之一 继承意味着派生类对象可以使用基类的方法 然而 构造函数在完成工作之前 对象并不存在 构造函数不能是虚函数 创建派生类对象时 将调
  • springboot项目获取真实用户ip(不是虚拟ip)

    最近在工作中遇见一个业务场景是获取用户真实的ip地址 就跟现在网上评论展示ip一样的业务场景 然后自己就去了解了一下 1 pom依赖配置
  • 数据库模式分解(应该比较易懂吧)

    数据库模式分解 部分函数依赖 函数依赖的确定 1对1的关系时 有两个函数依赖 1对多时 有一个函数依赖 多对多时 没有函数依赖 函数依赖类型 右 边 不 为 左 边
  • DVWA-XSS (Reflected)

    大约 跨站点脚本 XSS 攻击是一种注入问题 其中恶意脚本被注入到原本良性和受信任的网站上 当攻击者使用 Web 应用程序发送恶意代码 通常以浏览器端脚本的形式 时 就会发生 XSS 攻击 给其他最终用户 允许这些攻击成功的缺陷非常普遍 并
  • 机器学习论文总结

    作者简介 洪亮劼 Etsy数据科学主管 前雅虎研究院高级经理 长期从事推荐系统 机器学习和人工智能的研究工作 在国际顶级会议上发表论文20余篇 长期担任多个国际著名会议及期刊的评审委员会成员和审稿人 责编 何永灿 欢迎人工智能领域技术投稿
  • csgo修改服务器时间,csgo热身时间参数指令 热身时间怎么改

    反恐精英 全球攻势 简称CS GO 是一款由VALVE与Hidden Path Entertainment合作开发的第一人称射击游戏 游戏中可以更改相关控制命令 下面带来csgo热身时间参数指令 热身时间怎么改方法 csgo热身时间参数指令
  • 多系统集成:vue大型项目之分模块运行/打包

    本文以vue cli3 为例 实现多系统集成下的分模块打包 分模块打包方式多种多样 下文可适用于多系统之间互不干扰 主系统可集成各子系统 各子系统又可单独运行的业务场景 一 目标 我们要实现什么 所谓分模块打包 那么各个模块内就必须得有自己
  • 使用Arthas排查问题

    简介 Arthas 是Alibaba开源的Java诊断工具 深受开发者喜爱 当你遇到以下类似问题而束手无策时 Arthas可以帮助你解决 这个类从哪个 jar 包加载的 为什么会报各种类相关的 Exception 我改的代码为什么没有执行到
  • ThreadLocal详解

    如果有兴趣了解更多相关内容 欢迎来我的个人网站看看 瞳孔空间 一 基本介绍 ThreadLocal类能提供线程内部的局部变量 这种变量在多线程环境下访问时能保证各个线程的变量相对独立于其他线程内的变量 ThreadLocal实例通常来说都是