ThreadLocal详解及ThreadLocal源码分析

news2024/9/21 18:44:12

提示:ThreadLocal详解、ThreadLocal与synchronized的区别、ThreadLocal的优势、ThreadLocal的内部结构、ThreadLocalMap源码分析、ThreadLocal导致内存泄漏的原因、要避免内存泄漏可以用哪些方式、ThreadLocal怎么解决Hash冲突问题、避免共享的设计模式、ThreadLocal的场景面试题、并发编程的相关设计

文章目录

  • 前言
  • 一、ThreadLoacl介绍
    • 1、什么是ThreadLocal
    • 2、基本使用
    • 3、使用案例
      • 3.1、不使用ThreadLocal(存在并发问题)
      • 3.2、使用ThreadLocal
    • 4、ThreadLocal与synchronized的区别?
      • 4.1、synchronized代码
      • 4.2、ThreadLocal代码
    • 5、ThreadLocal的优势
    • 6、ThreadLocal在spring事务中的应用
  • 二、底层原理
    • 1、ThreadLocal的内部结构
    • 2、这样设计的好处?
    • 3、ThreadLocalMap源码分析
    • 4、ThreadLocal怎么解决Hash冲突问题(源码角度)?
      • 4.1、threadLocal的set方法源码:
      • 4.2、threadLocal的set方法
  • 三、ThreadLocal的场景面试题
    • 1、ThreadLocal导致内存泄漏的原因?
    • 2、那明明是弱引用,也会有内存泄漏的问题,为何还要用弱引用呢?
    • 3、要避免内存泄漏可以用哪些方式?
  • 四、避免共享的设计模式
    • 1、不变性模式
      • 1.1、概念
      • 2、不变性模式的好处:
      • 3、如何实现不可变性:
    • 2、写时复制模式
    • 3、线程本地存储模式(Thread-Specific Storag)
  • 总结


前言

ThreadLocal是并发编程中的一块内容, 工作中也有一定的使用频率,今天与大家一起探讨一下ThreadLocal的源码,同时也为自己以后的查阅提供一些资料。本人水平有限,如有误导,欢迎斧正,一起学习,共同进步!


一、ThreadLoacl介绍

1、什么是ThreadLocal

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

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

2、基本使用

在使用前,我们先来认识几个ThreadLocal的常用方法

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

3、使用案例

3.1、不使用ThreadLocal(存在并发问题)

因为是5个线程,操作同一个共享变量(content),所以会有并发问题。

package com.zheng.test.threadLocak;

/**
 * @author: ztl
 * @date: 2024/07/07 23:08
 * @desc:
 */
public class ThreadLocalDemo {

    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLocalDemo demo = new ThreadLocalDemo();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println(Thread.currentThread().getName() + "--->" +
                            demo.getContent());
                }
            });
            thread.setName("线程" + i);
            thread.start();
            
            
            // 执行结果:
//            线程0--->线程1的数据
//            线程3--->线程3的数据
//            线程1--->线程1的数据
//            线程4--->线程4的数据
//            线程2--->线程2的数据
        }
    }
}

3.2、使用ThreadLocal

使用ThreadLocal就不会有并发问题了,代码:

public class ThreadLocalDemo2 {

    private static ThreadLocal<String > threadLocal = new ThreadLocal<>();
    private String content;

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

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

    public static void main(String[] args) {
        ThreadLocalDemo2 demo = new ThreadLocalDemo2();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println(Thread.currentThread().getName() + "--->" +
                            demo.getContent());
                }
            });
            thread.setName("线程" + i);
            thread.start();


            // 执行结果:
//            线程0--->线程0的数据
//            线程3--->线程3的数据
//            线程1--->线程1的数据
//            线程4--->线程4的数据
//            线程2--->线程2的数据
        }
    }
}

4、ThreadLocal与synchronized的区别?

synchronized是独占锁,相当于串行执行了,那多线程就没啥意义了, 你执行完毕,才到我。
而ThreadLocal是并发执行,效率更高.、

synchronizedthreadLocal
原理同步机制采用"以时间换空间"的方式,只提供了一份变量,让不同的线程排队访问采用“以空间换时间”的方式,为每个线程都提供了一份变量的副本,从而实现同时访问而互不干扰
侧重点多个线程之间访问公共资源的同步多线程中让每个线程之间的数据互相隔离

4.1、synchronized代码

synchronized代码如下:

public class ThreadLocalDemo3 {

    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLocalDemo3 demo = new ThreadLocalDemo3();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (demo){
                        demo.setContent(Thread.currentThread().getName() + "的数据");
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    long end = System.currentTimeMillis();
                    System.out.println(Thread.currentThread().getName() + "--->" +
                            demo.getContent()+"耗时"+(end - start) + "毫秒");
                }

            });

            thread.setName("线程" + i);
            thread.start();

        }

    }
}

执行结果:
线程0—>线程4的数据耗时2013毫秒
线程4—>线程4的数据耗时4019毫秒
线程3—>线程3的数据耗时6026毫秒
线程2—>线程2的数据耗时8034毫秒
线程1—>线程1的数据耗时10041毫秒

4.2、ThreadLocal代码

ThreadLocal代码如下:

public class ThreadLocalDemo4 {

    private static ThreadLocal<String > threadLocal = new ThreadLocal<>();
    private String content;

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

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

    public static void main(String[] args) {
        ThreadLocalDemo4 demo = new ThreadLocalDemo4();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    long end = System.currentTimeMillis();
                    System.out.println(Thread.currentThread().getName() + "--->" +
                            demo.getContent()+"耗时"+(end - start) + "毫秒");
                }
            });
            thread.setName("线程" + i);
            thread.start();
        }
    }
}

执行结果,就是:
线程4—>线程4的数据耗时2004毫秒
线程0—>线程0的数据耗时2004毫秒
线程2—>线程2的数据耗时2004毫秒
线程3—>线程3的数据耗时2004毫秒
线程1—>线程1的数据耗时2004毫秒

5、ThreadLocal的优势

在一些特定的场景下,ThreadLocal有两个突出的优势:

  1. 传递数据:保存每个线程绑定的数据,有需要的地方可以直接获取,避免参数直接传递带来的代码耦合问题
  2. 线程隔离:各线程之间的数据相互隔离又具备并发性,避免同步方式带来的性能损失(比如上面的synchronized,要比ThreadLocal慢的多)

6、ThreadLocal在spring事务中的应用

spring的事务就借助了ThreadLocal类。spring会从数据库连接池中获的一个connection,然后把connection放进ThreadLocal中,也就和线程绑定了,事务需要提交或者回滚,只要从ThreadLocal中拿到connection进行操作。
为何spring的事务要借助ThreadLocal类?
以下面的代码为例,主要有3个步骤:
事务准备阶段:1-3行
业务处理阶段: 4-6行
事务提交阶段: 7行
很明显,不管是事务的开启、执行具体的sql、提交事务、都是需要数据库连接(connction)的。如果我们把控制事务的代码放在dao层,那肯定是需要一个字段比如说connectionObject字段,来存储这个connection对象,毕竟我们都是需要一个数据库连接的(如何让三个DAO使用同一个数据源连接呢?我们就必须为每个DAO传递同一个数据库连接,要么就是在DAO实例化的时候作为构造方法的参数传递,要么在每个DAO的实例方法中作为方法的参数传递。)。那我们如果只调用一个dao层,这样还算合适。那如果我们要连续操作3个dao呢,30个dao呢,难道要这30个dao,每个dao中都加一个connectionObject字段,然后把这30个dao中,挨着传递下去这个connection连接吗?毕竟我们一定是要基于同一个connection连接来操作的。那要保证这30个dao一定是同一个connection,那肯定要把这个connection传递下去,如果纯代码实现的话,那必然要加一个字段,然后挨着传递下去的。这样实现,肯定是不如直接把connection存在线程的threadlocal中,然后直接从线程的threadlocal中获取,这样也不用传递,还能获得同一个connection,还没有并发问题,肯定是比代码硬写要优美的多的。

1 dbc = new DataBaseConnection();//第1行
2 Connection con = dbc.getConnection();//第2行
3 con.setAutoCommit(false);// //第3行
4 con.executeUpdate(...);//第4行
5 con.executeUpdate(...);//第5行
6 con.executeUpdate(...);//第6行
7 con.commit();第7行

web容器中,每个完整的请求周期都会由一个线程来处理。因此,如果我们能把一些参数用threadlocal绑定到线程的话,就可以实现参数共享(隐形共享),结合spring中的ioc和aop,就可以很好的解决这一点。只要将一个数据库连接放到threadLocal中,当前线程执行时,直接从threadLocal中获取,是比较方便的。

二、底层原理

1、ThreadLocal的内部结构

如果我们不去看源码的话,可能以为ThreadLocal是这么设计的:创建一个map,其中map的key是thread,v是要存的内容。jdk早期版本的threadLocal确实是这么设计的,但现在早已不是了(因为有各种缺点,也就是后面设计方案的优点)。

在jdk8中,ThreadLocal的设计是:每个Thread都维护一个自己的ThreadLocalMap,这个map的key是ThreadLocal实例本身,value是真正要存储的值Object,具体的过程:

  1. 每个Thread线程内部都有一个Map(ThreadLocalMap)
  2. Map中存储ThreadLocal对象(key)和线程的变量副本(value)
  3. Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责像map获取和设置线程的变量值
  4. 对于不同的线程,每次获取副本时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互补干扰。
    在这里插入图片描述

2、这样设计的好处?

  1. threadLocal存的数据(entity)会变小。同时防止了大的对象。按最开始的设计方案:我有一个大的Map,key是线程,v是数据。那来一个线程,我就得存一个线程,再来一个线程,我在存第二个。。那100个线程,1000个线程,我同时存1000份的key、value,那这个对象也太大了。然后存的entity也太多了。但是如果分开的话,是每个线程都有自己独立的threadLocal,最起码不会有大的Map对象。而且我用到了threadLocal才有,不用就没有。
  2. 减少内存的使用。还有一个好处就是,我存储到Thread内部的话,一旦Thread销毁了,那么对应的ThreadLocalMap也会随之销毁。能减少内存的使用。毕竟如果用原始的设计的话,你这个thread在threadLocalmap中添加了,添加的时候key是thread,v是数据。那你这个thread一旦被销毁了,我threadLocalMap可不会销毁,毕竟我又不是只有你一个thread,我还有别的thread呢,我总不能因为你死了,我就直接全部销毁。我还得专门去销毁你这个单独的thread,不然就浪费资源,我要是单独消耗,也好消耗我的性能,那还真不如,是在thread内部添加上threadLocalMap对象,一旦thread消耗了,那threadLocalMap也自动销毁了。

3、ThreadLocalMap源码分析

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map功能,其内部的entry也是独立实现。
在这里插入图片描述
entry是真正存储数据的,entry继承了WeakReference,并用ThreadLocal作为key。其目的是将threadLocal对象的生命周期和线程声明周期解绑
强引用:就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表名对象还“活着”,垃圾回收器就不会回收这种对象
弱引用:(WeakReference),垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收他的内存。

4、ThreadLocal怎么解决Hash冲突问题(源码角度)?

threadLocal是通过线性探测法来解决hash冲突的。可以通过阅读源码来验证:

4.1、threadLocal的set方法源码:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

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);
}

这个就是单纯的说,如果map不为空,则将参数设置到map中;如果map为空,则创建map,并设置初始值。

  • int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 的意思是说,求模,来获取在map中的巢(其实就是map中的数组,对应的位置)。就比如说,101 % 16,任何数对16取模,结果一定是0-15的数据,也就是为了确定,是存到哪个节点中的。
  • setThreshold(INITIAL_CAPACITY); 这个点进去就是threshold = len * 2 / 3; ,意思是,当占了2/3的空间以后,就开始扩容了。

4.2、threadLocal的set方法

map.set(this, value);(上面代码块的5行)源码:

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         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();
}

上面这段的意思,简单的说就是:

  • 没元素,直接插入
  • 有元素,判断key相等不相等,不相等,就往后移一位,看有元素没,没元素直接插入,
  • 有元素,key相等,直接覆盖
    e = tab[i = nextIndex(i, len)]) { 上面代码中的这行点击去就是:
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

也就是说,他是个环形数组: 如果数组的当前位置+1到达了数组的最后,那么就返回0(数组的第一个位置);如果数组的当前位置+1还没达到数组的最后,那么就往后移动一位。也就是,这是一个收尾相连的数组。从这里,我们可以看出来:他是通过线性探测法去解决hash冲突的。毕竟冲突了以后他往后移动一位继续比较,有位置就塞进去,没位置就继续往后走,可不就是线性探测法嘛。

三、ThreadLocal的场景面试题

1、ThreadLocal导致内存泄漏的原因?

从下面的图可以看出来: 当前线程指向map,map指向entry,entry中的key指向ThreadLocal,entry的v指向自己的my value。一旦触发了垃圾回收,key与ThreadLocal之间的引用是弱引用,直接回收了;但map跟entry之间是强引用,entry的v和v的内容之间也是强引用(v和v的内容的强引用是导致内存泄漏的真实原因)。其实要想内存泄漏,必须满足两点:

  1. 没有手动删除这个entry
  2. 当前线程(currentThread)仍在执行。因为我们很可能使用线程池之类的技术,导致这个线程一直都没结束。 总结一下,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应的key就会导致内存泄漏。
    那怎么解决这种内存泄漏呢?:每次用完threadLocal,都给remove掉对应的threadLocal就行
    在这里插入图片描述

2、那明明是弱引用,也会有内存泄漏的问题,为何还要用弱引用呢?

  1. 如果是弱引用的话,是只有entry中的v会有内存泄漏,一旦我用强引用的话,我entry中的key也会有内存泄漏的问题的。能优化一点是一点,肯定是用弱引用啊。而且ThreadLocalMap中的set/getEntry方法中,会对key为null(也就是ThreadLocal为null,就是弱引用被gc掉的那个)进行判断,如果为null的话,那么会对value置为null的。
  2. 比用强引用来说,多一层保障:弱引用的threadLocal会被回收,对应的value在下一次ThreadLocalMap调用get、set、remove任一方法的时候会被清除。

3、要避免内存泄漏可以用哪些方式?

  1. 使用完ThreadLocal,调用其remove方法删除对应的entry
  2. 使用完threadLocal,当前的thread也随之运行结束。

相比于第一种方式,第二种方式更不好控制,特别是使用线程池的时候,线程结束是不会被销毁的,因此更推荐使用方式1,remove掉对应的entry。

四、避免共享的设计模式

不变性模式、写时复制模式、线程本地存储模式,都可以避免共享。

  1. 使用时需要注意不变形模式的属性不可变性
  2. 写时复制模式需要注意拷贝的性能问题
  3. 线程本地存储模式需要注意异步执行问题

1、不变性模式

1.1、概念

多个线程同时读写同一个共享变量会存在并发问题。同时读写才会有问题。那我只能读,不能写,就没这个问题了。不变性模式是一种创建不可变对象的设计模式。即对象一旦创建成功后,就不能在修改。在多线程下,使用不可变对象可以避免线程安全问题,并提高程序的性能和可读性。

2、不变性模式的好处:

  1. 线程安全:不可变对象在多线程环境下不需要同步操作,可以避免线程安全问题
  2. 可读性:不可变对象在创建后不可修改,可以更加清晰的表达对象的含义和作用
  3. 性能:由于不可变对象的状态不可变,可以进行更有效的缓存和优化操作
  4. 可测试性:不可变对象对单元测试非常友好,可以更容易的进行测试和验证。

3、如何实现不可变性:

不可变性模式的主要思想是通过将对象的状态设置为final和私有,并提供只读方法来保证对象的不可变性。在创建不可变对象时,需要确保对象的所有属性都是不可变的,在创建后不会被修改,同时还需要注意不可变对象间的引用关系,以避免出现对象的状态变化。
jdk中很对类都具备不可变性,例如:Sting和Long、Integer、Double 等基础类型的包装类都具备不可变性,他们都遵循了不可变类的三点要求:和属性都是final的,所有方法均是只读的

2、写时复制模式

该模式的基本思想是:在共享数据被修改时,先将数据复制一份,然后对副本进行修改,最后再讲副本替换为原始的共享数据
不可变对象的写操作往往都是通过使用Copy-on-Write方法解决的,当然,Copy-on-Write不局限于这个模式。Copy-on-Write才是最简单的并发解决方案,他是如此的简单,以至于Java中的数据类型String、Integer、Long 都是基于Copy-on-Write方案实现的。
Copy-on-Write的缺点是消耗内存。每次修改都需要复制一个新的对象,好在随着gc算法的成熟,这种内存消耗渐渐可以接收了。在实际工作中,如果读多写少的场景,可以使用Copy-on-Write

3、线程本地存储模式(Thread-Specific Storag)

该模式的基本思想的为每个线程创建独立的存储空间,用于存储线程私有的数据ThreadLocal类实现了该模式
如果你在并发场景中使用一个线程不安全的工具,最简单的方案就是避免共享。避免共享有两种方案,一种方案是将这个类作为局部变量使用,另一种方案就是线程本地存储模式。局部变量的方案的缺点是在高并发场景下会频繁创建对象,而现场本地存储方案,每个线程值需要创建一个工具类的实例,所以不存在频繁创建对象的问题,

线程本地存储通常用于以下场景

  1. 保存上下文信息:在多线程环境中,每个线程都有自己的执行上下文,包括线程的状态、环境变量、运行时状态等。
  2. 管理线程安全对象:在多线程环境中,共享对象通常需要同步操作以避免竞争条件,但是有些对象多线程安全的么可以被多个线程同时访问而不需要同步操作。线程本地存储可以用来管理这些线程安全对象,使得每个线程都可以独立的访问自己的对象实例,而不需要进行同步操作。
  3. 实现线程的特定的行文:优先应用程序需要再每个线程中执行特定的行为,比如记录日志、统计数据、授权访问等。线程本地存储可以实现这个线程的特定行为,而不需要跟其他线程进行协调

总结

有时候我们阅读源码,不单单是为了看他的源码是啥样的,更多的是给我们扩展了视野,以后工作中遇到类似的问题,可能有一个新的解决方案或是方向,来帮助我们更好更快的解决各种问题。

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

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

相关文章

Android DexOpt七种触发流程解析【原创硬核】

Android 13 DexOpt七种触发流程解析 众所周知&#xff0c;DexOpt是安卓应用性能优化非常重要的手段&#xff0c;相当于将应用对虚拟机的多层调用直接转化成了arm机器码。Dex优化过和没优化过&#xff0c;效果千差万别。本文深入解析android系统DexOpt机制的触发流程。 1 DexOpt…

如何学习自动化测试工具!

要学习和掌握自动化测试工具的使用方法&#xff0c;可以按照以下步骤进行&#xff1a; 一、明确学习目标 首先&#xff0c;需要明确你想要学习哪种自动化测试工具。自动化测试工具种类繁多&#xff0c;包括但不限于Selenium、Appium、JMeter、Postman、Robot Framework等&…

安全编程的代码示例

一、python代码示例 一个安全编程的示例是对输入进行严格的验证和过滤。比如&#xff0c;当用户在网页上输入用户名和密码时&#xff0c;应该对这些输入进行验证&#xff0c;防止恶意用户输入恶意代码或进行 SQL 注入等攻击。下面是一个简单的示例代码&#xff1a; import…

OpenFeign微服务调用组件

一、跨服务、跨进程调用方式 1)Httpclient 使用Httpclient我们首先需要引入依赖 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </depend…

Meta/东京电子/FPT软件等共同贡献,Aitomatic发布首个半导体行业开源大模型,「锁死」企业技术自主权

2024 年初&#xff0c;研究机构 Market.us 发布报告预测&#xff0c;全球半导体市场规模将大幅增长&#xff0c;预计 2024 年可达到 6,731 亿美元&#xff0c;到 2032 年预计将增长至 1.3 万亿美元。 毫无疑问&#xff0c;这个万亿级市场与充满想象空间的 AI 密不可分。技术层…

【根号分治】 通知

通知 分析&#xff1a; 这道题根号分治看起来就没有前面几题那么明显了 emm当然也可能是我境界还没到 我们考虑如果暴力修改&#xff0c;复杂度是 O ( n m ) O(nm) O(nm)&#xff0c;其实m为这个点的度数 考虑根号分治的思想&#xff0c;我们令 m M m\sqrt M mM ​ 并命度数大…

申瓯通信设备有限公司在线录音管理系统(复现过程)

漏洞简介 申瓯通信设备有限公司在线录音管理系统 index.php接口处存在任意文件读取漏洞&#xff0c;恶意攻击者可能利用该漏洞读取服务器上的敏感文件&#xff0c;例如客户记录、财务数据或源代码&#xff0c;导致数据泄露 一.复现过程 fofa搜索语句:title"在线录音管…

idea显示properties文件中文乱码的解决方法

1.如下 File-》Settings-》File Encodings,修改如下图中绿框标注的内容 2.点击Apply-->OK 3.修改完成后显示

ROS八股

目录 一、ros1和ros2的区别是什么&#xff1f; 二、rostopic 和 rosserver的区别是什么&#xff1f; 三、讲一下ros的navigation框架 一、ros1和ros2的区别是什么&#xff1f; ROS 1和ROS 2是两个主要版本的机器人操作系统&#xff0c;它们在多个方面存在显著差异&#xff…

【Stable Diffusion】(基础篇七)—— lora

lora 本系列博客笔记主要参考B站nenly同学的视频教程&#xff0c;传送门&#xff1a;B站第一套系统的AI绘画课&#xff01;零基础学会Stable Diffusion&#xff0c;这绝对是你看过的最容易上手的AI绘画教程 | SD WebUI 保姆级攻略_哔哩哔哩_bilibili 除了大模型和VAE之外&…

MySQL:集合运算符

集合运算符 MySQL中的 集合运算符&#xff08;Set operators&#xff09;主要用于结合两个或多个SELECT语句的结果集&#xff0c;这些结果集应该具有相同的列数和数据类型&#xff0c;以便能够进行比较或合并。 需要注意的是&#xff0c;MySQL本身并没有直接称为“Set operat…

Flask目录结构路由重定向简单实例讲解——轻量级的 Python Web 框架

假设一个flask目录结构如下&#xff1a; my_flask_app/ │ ├── app.py ├── routes/ │ ├── __init__.py │ ├── ZhejiangProvince/ │ │ ├── __init__.py │ │ ├── la.py │ │ └── el.py │ ├── GuangdongProvince/ │ │ ├…

常见服务限流方法

一、令牌桶算法&#xff08;Token Bucket&#xff09; 原理其实很简单&#xff0c;就是设置同一时刻服务器能处理请求的最大数量&#xff0c;如果超过这个数据&#xff0c;则需要等待&#xff0c;或者不处理请求。相当于设置最大并发量&#xff0c;但是细节是&#xff0c;还设…

解决nginx端口转发后,获取不到真实IP问题

文章目录 1&#xff0c;设置nginx端口转发1.2&#xff0c;无法获取客户端真实IP 2&#xff0c;nginx配置文件增加配置&#xff0c;保留客户端信息2.2&#xff0c;可以看到真实IP信息 1&#xff0c;设置nginx端口转发 location /AWAPI/ {proxy_pass http://172.28.43.19:9607; …

组件化开发

1.组件化开发 组件化&#xff1a;一个页面可以拆分成一个个组件&#xff0c;每个组件有着自己独立的结构[html]、样式[css]、行为 [js]。好处&#xff1a;便于维护&#xff0c;利于复用 → 提升开发效率。组件分类&#xff1a;普通组件、根组件。比如&#xff1a;下面这个页面…

二级MySQL(十二)——分组聚合查询

首先整理常用的聚合函数&#xff1a; 函数名说明COUNT&#xff08;*&#xff09;记录数COUNT&#xff08;列名&#xff09;一列的记录数MAX&#xff08;列名&#xff09;一列的最大值 MIN&#xff08;列名&#xff09; 一列的最小值 SUM&#xff08;列名&#xff09;一列…

M12电连接器航插插座L-code

M12电连接器概述 M12电连接器是一种广泛应用于工业自动化、传感器、仪器仪表、数据通信和控制系统等领域的圆形连接器。它的核心特点在于其小巧的尺寸、强大的多信号传输能力和出色的防水性能&#xff0c;使其成为众多工业应用的首选。M12连接器通常具有3至12个引脚&#xff0…

redis主从复制、哨兵模式、集群

redis集群 高可用 redis集群的三种模式&#xff1a; 1.主从复制&#xff08;奇书 3台 一主两从&#xff09; 2.哨兵模式 &#xff08;3台 一主两从&#xff09; 3.cluster &#xff08;集群 6 333&#xff09; 主从复制&#xff1a;喝MySQL的主从复制类似&#xff0c;主可以写…

vite创建Vue2项目(配图详细)

参考文章&#xff1a;vite项目生成vue3并引入element-ui vite脚手架生成vue项目及其配置_viteconfig配置-CSDN博客 Vite 默认支持 Vue 3&#xff0c;但你也可以使用 Vite 来搭建 Vue 2 的项目。不过&#xff0c;这需要一些额外的配置&#xff0c;因为 Vue 2 不支持原生的 ES …