浅聊 【ThreadLocal】(超级详细)

news2024/12/23 12:41:47

写在开始 :
本文主要讲述 : ThreadLocal简介; 常用API; demo案例; 特点引用场景;以及部分底层原理源码内容。

引言 :

从常见面试题看 ThreadLocal:
**①解释 **: ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分
配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时
实现了线程内的资源共享;
②案例:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的
ThreadLocal中,从而保证每个线程都在各自的 Connection 上进行数据库的操
作,避免A线程关闭了B线程的连接。

public class ThreadLocalTest {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
    public static void main(String[] args) {
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            threadLocal.set("itcast"); // set(v)方法 : 设置值
            print(name);
            System.out.println(name + "-after remove : " +
            threadLocal.get());        // get()方法:获取值
        }, "t1").start();
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            threadLocal.set("itheima");
            print(name);
            System.out.println(name + "-after remove : " +
            threadLocal.get());
        }, "t2").start();
    }
    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + threadLocal.get());
        //清除本地内存中的本地变量
        threadLocal.remove();         // remove()方法 清除值
    }
}

③源码
ThreadLocal本质来说就是一个线程内部存储类,从而让多个线程只操作自己内
部的值,从而实现线程数据隔离;
image.png
在ThreadLocal中有一个内部类叫做ThreadLocalMap,类似于HashMap
ThreadLocalMap中有一个属性table数组,这个是真正存储数据的位置
Ⅰ:set方法:
image.png
Ⅱ:get/remove方法
image.png

内存泄漏问题
每一个Thread维护一个ThreadLocalMap,在ThreadLocalMap中的Entry对象继承
了WeakReference。其中key为使用弱引用的ThreadLocal实例,value为线程变量
的副本;
引用类型 : ①弱引用:内存不太够时候优先回收;②强引用:不会被回收
使用 ThreadLocal 时候 ,强烈建议:务必手动remove。

正文第一部分–编程实战

1.ThreadLocal 简介

ThreadLocal 见名知意 : 线程本地 或者 本次线程;
但是从它实际作用而言, 可能解释为 【线程局部变量】or 【线程本地变量】更为合适。

2.ThreadLocal作用

ThreadLocal是java.lang包中的一个泛型类,可以实现为线程创建独有变量,这个变量对于其他线程是隔离的,也就是线程本地的值,这也是ThreadLocal名字的来源;

package java.lang

public class ThreadLocal<T>{}

每个使用该变量的线程都要初始化一个完全独立的实例副本(也就是变量的本地拷贝),不存在多线程间共享问题

3.ThreadLocal的 API 方法

ThreadLocal用来存储当前线程的独有数据,相关API就是存值,取值,清空值的简单操作

  • withInitial:创建一个ThreadLocal实例,并给定初始值【JDK8推出的新方法,一般都是用该方法初始化ThreadLocal】
  • get:返回当前线程ThreadLocal的值,如果没有设置值返回null
  • set:设置当前线程ThreadLocal的值
  • remove:删除当前线程ThreadLocal的值
  • initialValue:此方法默认返回null, 通过ThreadLocal构造方法初始化时一般重写此方法,来设置初始值,在JDK8之后通过withInitial方法初始化

4.初始化ThreadLocal

初始化ThreadLocal有三种方式:

  • 直接通过构造方法创建,此时初始值为null
  • 通过构造方法同时重写initialValue方法给定初始值
  • 通过JDK8的withInitial()静态方法创建,可以通过Lambda直接给初始值【推荐使用】

①构造方法

@Test
public void test1() {
    // 通过构造方法,初始化ThreadLocal
    ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    // 未给定初始值,通过get方法获取值为null
    System.out.println(threadLocal.get());
}


@Test
public void test2() {
    // 初始化ThreadLocal
    ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    System.out.println("设置前  -->" + threadLocal.get()); // null
    // 通过 set 方法,设置初始值
    threadLocal.set(1);
    System.out.println("设置后  -->" + threadLocal.get()); // 1
}

②重写initialValue方法

@Test
public void test3() {
    // 初始化ThreadLocal,重写initialValue方法设置默认值
    ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            // 设置,初始值为1024
            return 1024;
        }
    };
    
    // 启动5个线程
    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":threadLocal初始值 -->" + threadLocal.get());
        },"线程:" + i).start();
    }
}

打印结果 : 5个线程每个线程的初始值都为1024

// 返回当前线程ThreadLocal的初始值,但是在ThreadLocal中默认实现返回为null
// 这个值和具体的泛型类型有关,通常需要根据实际需求重写此方法,定义初始值

protected T initialValue(){
    return null;
}

③withInitial静态方法

// JDK8中新增了withInitial静态方法接收Supplier供给型函数接口设置初始值
@Test
public void test4() {
    // 通过withInitial方法设置初始值
    ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> {
        return 100;
    });
    // 启动5个线程
    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":threadLocal初始值 -->" + threadLocal.get());
        },"线程:" + i).start();
    }
}

打印结果 :
启动5个线程之后初始值为100,
强烈推荐此种方式来实现ThreadLocal的初始化
推荐通过withInitial静态方法实现ThreadLocal的初始化
如果线程内需要修改值则可以使用set方法,如果需要获取值则使用get方法
ThreadLocal数据存储:一个ThreadLocal实例,在每个线程中都有独自的初始化副本,接下来每个线程对ThradLocal的操作都在线程内,对其他线程隔离

5.demo案例

背景说明:

@Test
public void demo() {
    // 通过withInitial方法设置初始账户余额为0
    ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    
    // 设置随机数
    Random random = new Random();
    
    // 通过map集合映射线程名字
    Map<Integer, String> map = new HashMap<>();
    map.put(0,"账户一");
    map.put(1,"账户二");
    map.put(2,"账户三");
    
    // 启动3个线程,分别为3个账户
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            // 去存钱5次
            for (int j = 0; j < 5; j++) {
                // 每次随机,金额在200以内
                int money = random.nextInt(200);
                
                // 金额加1
                threadLocal.set(money + threadLocal.get());
            }
            System.out.println(Thread.currentThread().getName() + "共存入:" + threadLocal.get() + "元的钱");
        },map.get(i)).start();
    }
}

三个账户各自存款五次,金额实现单独记录
打印结果 : 账户一共存入:499元; 账户二共存入:528元; 账户三共存入:799元;

线程池实现

实际开发对于多线程的场景都推荐使用线程池实现,可以避免线程频繁创建和销毁的资源浪费,使用线程池是一定要记得在finally代码块中关闭线程池

@Test
public void test6() {
    // 通过withInitial方法设置初始值
    ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    // 设置随机数
    Random random = new Random();

    Map<Integer, String> map = new HashMap<>();
     map.put(0,"账户一");
    map.put(1,"账户二");
    map.put(2,"账户三");

    // 开启有3个核心线程的线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    try {
        // 3个账户
        for (int i = 0; i < 3; i++) {
            // 记录当前循环值,映射对应的账户
            int index = i;
            threadPool.submit(() -> {
                // 设置线程名:Thread.currentThread().getName()为默认的线程池给的名字,方便查看是哪一个线程执行的此任务
                Thread.currentThread().setName(Thread.currentThread().getName() + map.get(index));
                // 存钱,比如5次
                for (int j = 0; j < 5; j++) {
                    // 每家随机,金额200以内
                    int money = random.nextInt(200);
                    // 金额加1
                    threadLocal.set(money + threadLocal.get());
                }
                System.out.println(Thread.currentThread().getName() + "共:" + threadLocal.get() + "元");
            });
        }
    }catch (Exception e) {
        e.printStackTrace();
    }finally {
        // 关闭线程池
        threadPool.shutdown();
    }
}

假设运行程序的电脑为8核,可以通过线程池中的3个核心线程,同时执行3条任务,此时的结果没有任何问题
打印结果:
pool-1-thread - 1 账户一共:477元
pool-1-thread - 3 账户三共:777元
pool-1-thread - 2 账户二共:557元

线程池中线程可复用引发的问题

如果此时又多出来一个账户,或者核心线程数变为2,即任务数大于核心线程数,会复用线程处理其他任务,即一个线程需要处理多个任务,这里减少核心线程数为例演示:

@Test
public void test6() {
    ......
    // 修改线程池核心线程数为2,其他不变
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    ......
}

**原因在于:**1号线程处理完账户一之后,发现账户二任务没有执行,此时1号线程处理两个任务,ThreadLocal也还是同一个,即处理账户二任务时,ThreadLocal的值为账户一任务处理后的值,并不是初始值0;
在阿里Java开发规范中强制要求回收自定义的ThreadLocal变量

通过try块将任务逻辑包裹,在finally中通过remove方法回收该任务执行后的ThreadLocal值

@Test
public void test7() {
    ......
    try {
        ......
            try {
                for (int j = 0; j < 5; j++) {
                    // 随机的200以内
                    int money = random.nextInt(200);
                    // 金额加1
                    threadLocal.set(money + threadLocal.get());
                }
                System.out.println(Thread.currentThread().getName() + "共:" + threadLocal.get() + "元");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 回收数据
                threadLocal.remove();
            }
    }catch (Exception e) {
        e.printStackTrace();
    }finally {
        // 关闭线程池
        threadPool.shutdown();
    }
}

回收ThreadLocal变量后,复用该线程也不会对后续程序造成影响

6.ThreadLocal特点

  • 统一设置初始值,每个线程可以通过set方法设置值,也可以通过get方法获取当前值
  • ThreadLocal被每一个线程单独持有副本,相互独立,只能在该线程内部使用
  • 如果配合线程池使用,线程可复用,需要调用remove方法回收数据,即重新设置为初始值,避免对后续程序造成影响和内存泄漏
  • ThreadLocal变量因为线程独立,所以不在线程安全问题

7.ThreadLocal应用场景

ThreadLocal 适用于如下两种场景

  • 每个线程需要自己独立的数据
  • 数据在线程内的共享,不需要在多线程之间共享

举例:

  • 游戏玩家个人的属性,装备,积分等
  • Spring中也通过ThreadLocal解决线程安全问题,在同一次请求响应的调用线程中,所有对象所访问的同一ThreadLocal变量都是当前线程所绑定的;

正文第二部分–底层原理

1.ThreadLocal线程安全原理

ThreadLocal存储数据是通过ThreadLocalMap实现,ThreadLocalMap底层是Entry数组,以键值对的形式存放,键是ThreadLocal对象,value是set()设置的值

  • 当执行set方法时:其value是保存在threadlocals变量中的
  • 当执行get方法时:就是读取threadlocals变量中的值。
  • 各个线程的数据互不打扰。一个线程可以存放多个ThreadLocal变量,他们都是存在了ThreadLocalMap中
/* 记录与此线程相关的ThreadLocal值。这个map由ThreadLocal类维护 */
ThreadLocal.ThreadLocalMap threadLocals = null;

首先,在Java中的线程是一个Thread类的实例对象!而一个实例对象中实例成员字段的内容肯定是这个对象独有的,所以如果将ThreadLocal线程本地变量作为Thread类的成员字段,就可实现线程私有,在Thread类中恰巧就是这么实现的,这个成员字段就是上述代码

map是一个在ThreadLocal中通过静态内部类定义的Map对象,保存了该线程中的所有本地变量【即一个线程可以使用多个ThreadLocal,存储在指定Thread的Map中】。ThreadLocalMap中的Entry的定义如下:

static class ThreadLocalMap{   // 静态内部类
    static class Entry extends WeakReference<ThreadLocal<?>>{ // 通过Entry对象维护数据,继承弱引用
        Object value;

        Entry(ThreadLocal<?> k,Object v){
            super(k); // 弱引用,内存不够优先回收
            value = v; // 强引用,不会回收
        }
    }
}
  • 每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
  • ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中并没有链表结构。
  • 我们还要注意Entry, 它的key是ThreadLocal<?> k ,继承自WeakReference, 也就是我们常说的弱引用类型。

  • Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap
  • ThreadLocalMap是ThreadLocal类中的静态内部类,通过内部类Entry记录值,
  • 在ThreadLocalMap中维护这一个Entry数组,因为一个线程可以使用多个ThreadLocal变量
  • Entry将该Thread中使用的ThreadLocal当做key,将值当做value【实际上key并不是ThreadLocal本身,而是它的一个弱引用】。
  • ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value

Set源码

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的threadLocals字段
    ThreadLocalMap map = getMap(t);
    // 判断线程的threadLocals是否初始化了
    if (map != null) {
        map.set(this, value);
    } else {
        // 没有则创建一个ThreadLocalMap对象进行初始化
        createMap(t, value);
    }
}

createMap方法的源码

void createMap(Thread t, T firstValue) {
    // 新建一个ThreadLocalMap
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

map.set方法的源码

/**
* 往map中设置ThreadLocal的关联关系
* set中没有使用像get方法中的快速选择的方法,因为在set中创建新条目和替换旧条目的内容一样常见,
* 在替换的情况下快速路径通常会失败(对官方注释的翻译)
*/
private void set(ThreadLocal<?> key, Object value) {
    // map中就是使用Entry[]数据保留所有的entry实例
    Entry[] tab = table;
    int len = tab.length;
    // 返回下一个哈希码,哈希码的产生过程与神奇的0x61c88647的数字有关
    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();
}

get方法原理

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 获取ThreadLocal对应保留在Map中的Entry对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 获取ThreadLocal对象对应的值
            T result = (T)e.value;
            return result;
        }
    }
    // map还没有初始化时创建map对象,并设置null,同时返回null
    return setInitialValue();
}

remove方法原理

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    // 键在直接移除
    if (m != null) {
        m.remove(this);
    }
}

2.ThreadLocal设计

JDK8之后,每个Thread维护一个ThreadLocalMap对象,这个Map的key是ThreadLocal,value是存储的是泛型值,其具体过程如下:

  • 每个Thread线程内部都有一个Map【ThreadLocalMap.threadlocals】;
  • Map里面存储ThreadLocal对象【key】和线程的变量副本【value】;
  • Thread内部Map由ThreadLocal维护,由ThreadLocal负责向map获取和设置变量值;
  • 对于不同的线程,每次获取副本值时,别的线程不能获取当前线程的副本值,就形成了数据之间的隔离。

这样设计的好处是:

  • 每个Map存储的Entry的数量变少,在实际开发过程中,ThreadLocal的数量往往要少于Thread的数量,Entry的数量减少就可以减少哈希冲突;
  • 当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存使用,早期的ThreadLocal并不会自动销毁。

使用ThreadLocal的好处

  • 保存每个线程绑定的数据,在需要的地方可以直接获取,避免直接传递参数带来的代码耦合问题;
  • 各个线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失。

3.ThreadLocal内存泄露问题

**内存泄露问题:**指程序中动态分配的堆内存由于某种原因没有被释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢或者系统奔溃等严重后果。内存泄露堆积将会导致内存溢出。

ThreadLocal的内存泄露问题一般考虑和Entry对象有关,Entry继承WeakReference也就是弱引用。**JVM会将弱引用修饰的对象在下次垃圾回收中清除掉。**这样就可以实现ThreadLocal的生命周期和线程的生命周期解绑。但实际上并不是使用了弱引用就会发生内存泄露问题,考虑下面几个过程:

强引用


当ThreadLocal Ref被回收了,由于Entry使用的是强引用,在Current Thread还存在的情况下就存在着到达Entry的引用链,无法清除掉ThreadLocal的内容,同时Entry的value也同样会被保留;也就是说就算使用了强引用仍然会出现内存泄露问题。

弱引用


当ThreadLocal Ref被回收了,由于在Entry使用的是弱引用,因此在下次垃圾回收的时候就会将ThreadLocal对象清除,这个时候Entry中的KEY=null。但是由于ThreadLocalMap中任然存在Current Thread Ref这个强引用,因此Entry中value的值任然无法清除。还是存在内存泄露的问题。

综上所述,使用ThreadLocal造成内存泄露的问题是因为:ThreadLocalMap的生命周期与Thread一致,如果不手动清除掉Entry对象的话就可能会造成内存泄露问题。因此,需要我们在每次在使用完之后需要手动的remove掉Entry对象。

避免内存泄露的两种方式:使用完ThreadLocal,调用其remove方法删除对应的Entry或者使用完ThreadLocal,当前Thread也随之运行结束。第二种方法在使用线程池技术时是不可以实现的。
所以一般都是自己手动调用remove方法,调用remove方法弱引用和强引用都不会产生内存泄露问题,使用弱引用的原因如下:
在ThreadLocalMap的set/getEntry中,会对key进行判断,如果key为null,那么value也会被设置为null,这样即使在忘记调用了remove方法,当ThreadLocal被销毁时,对应value的内容也会被清空。多一层保障!
:::info
TIP:
存在内存泄露的有两个地方:ThreadLocal和Entry中Value;最保险还是要注意要自己及时调用remove方法!!
:::

补充面试题部分

问题一:实际开发有用过ThreadLocal吗?

举例1 : 用来做用户信息上下文存储;
当系统应用是MVC架构的背景,用户每次登录访问接口,都会在请求头携带一个 token,控制层可以根据这个 token ,解析用户基本信息,那么在服务层,持久层也要用户信息,比如 rpc调用,更新用户获取等等,就可以采取如下两种方式:
方式一: 显式定义用户相关参数,比如账户,用户名等等,但是这样会大面积修改代码。这时候用 ThreadLocal ,控制层拦截请求把用户信息存入ThreadLocal,就能在需要的地方,取出ThreadLocal里存的用户数据;

其它有些场景的 cookie session 等数据隔离也可以通过 ThreadLocal 去实现;
之前引言案例举例的数据库连接池也用到 ThreadLcoal:
DB连接池的连接交给ThreadLocal进行管理,保证当前线程的操作都是一个Connection;

问题二:谈谈你对ThreadLocal的理解?

ThreadLocal 主要功能有两个,第一个是可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题,第二个是实现 了线程内的资源共享

问题三:ThreadLocal的底层原理实现?

①在ThreadLocal内部维护了一个一个 ThreadLocalMap 类型的成员变量,用来 存储资源对象
②当我们调用 set 方法,就是以 ThreadLocal 自己作为 key,资源对象作为 value,放入当前线程的 ThreadLocalMap 集合中
③当调用 get 方法,就是以 ThreadLocal 自己作为 key,到当前线程中查找关联 的资源值
④当调用 remove 方法,就是以 ThreadLocal 自己作为 key,移除当前线程关联 的资源值

问题四:ThreadLocal内存溢出?

  1. 因为ThreadLocalMap 中的 key 被设计为弱引用,它是被动的被GC调用释 放key,不过关键的是只有key可以得到内存释放,而value不会,因为value 是一个强引用。
  2. 在使用ThreadLocal 时都把它作为静态变量(即强引用),因此无法被动依 靠 GC 回收,建议主动的remove 释放 key,这样就能避免内存溢出。

小结

:::info

  • ThreadLocal更像是对其他类型变量的一层包装,通过ThreadLocal的包装使得该变量可以在线程之间隔离和当前线程全局共享。
  • 线程的隔离性和变量的线程全局共享性得益于在每个Thread类中的threadlocals字段。(从类实例对象的角度抽象的去看Java中的线程!!!)
  • ThreadLocalMap中Entry的Key不管是否使用弱引用都有内存泄露的可能。引起内存泄露主要在于ThreadLocal对象和Entry中的Value对象,因此要确保每次使用完之后都remove掉Entry!

:::

写在最后 : 码字不易,如果觉得还不错,或者对您有帮助,麻烦动动小手,点赞或关注,谢谢! 祝大家周末愉快!

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

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

相关文章

121、仿真-基于51单片机8路温度 ds18b20多路温度传感器检测仿真设计(Proteus仿真+程序+原理图+参考论文+任务书+流程图等)

摘 要 随着社会经济的高速发展和科技水平的不断进步&#xff0c;温度监控器的运用范围越来越广泛&#xff0c;也渐渐地发展到了核变站的温度监控。温度与人们的生活生产密切相关&#xff0c;比如在核变站的环境下&#xff0c;对温度的监控更是必不可少的&#xff0c;不但能保…

【代码随想录 | Leetcode | 第四天】数组 | 螺旋矩阵 | 59-54

前言 欢迎来到小K的Leetcode|代码随想录|专题化专栏&#xff0c;今天将为大家带来螺旋矩阵的分享✨ 目录 前言59. 螺旋矩阵 II54. 螺旋矩阵总结 59. 螺旋矩阵 II 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n…

ASIDE-Simulink接口预研报告

ASIDE-Simulink接口预研报告 2018年4月 1 引言 本报告描述ASIDE-Simulink接口的预先研究情况和结果。 2 目的 ASIDE-Simulink接口是ASIDE中的一个模块。它的主要功能是通过解析Simulink模型文件&#xff0c;把Simulink模型中的相关信息导入到ASIDE中&#xff0c;从而把Simu…

Vue-封装组件的案例

1.案例效果 封装要求&#xff1a; ①允许用户自定义title标题 ②允许用户自定义bgcolor背景色 ③允许用户自定义color文本颜色 ④MyHeader组件需要在页面顶部进行fixed固定定位&#xff0c;且z-index等于999 使用示例如下&#xff1a; <template><div class"…

独立、相关和正交的关系以及白噪声

注意如下边缘密度的计算&#xff1a; 第一个题&#xff1a;不独立&#xff0c;不相关&#xff0c;正交 第一个题&#xff1a;独立&#xff0c;不相关&#xff0c;正交 第一个题&#xff1a;独立&#xff0c;不相关&#xff0c;不正交

【第九天】面向程序设计_类

类 是一种数据结构&#xff0c;它可以包含数据&#xff0c;成员&#xff0c;常量和变量函数&#xff0c;成员方法&#xff0c;属性&#xff0c;构造函数和析构函数等和嵌套类型。 类的声明 在程序中类适用class关键字来声明的语法如下: class 类名 { }类的成员 类的定义包…

Coursier安装Scala报错Error downloading的解决方法

根据 Scala 官方目前的安装教程 https://docs.scala-lang.org/getting-started/index.html&#xff0c;我们下载 cs-x86_64-pc-win32.zip 并解压为 cs-x86_64-pc-win32.exe。在 PowerShell 中通过java --version确认 JVM 是否已安装&#xff0c;如果已安装&#xff0c;则切换到…

从代码到内容创作:程序员如何通过自媒体项目实现赚钱?

从代码到内容创作&#xff1a;程序员如何通过自媒体项目实现赚钱&#xff1f; 自媒体项目已成为程序员们实现赚钱的一种创新方式。通过将代码技术与内容创作结合&#xff0c;程序员可以在互联网上建立自己的品牌&#xff0c;并通过以下方式实现收入增长&#xff1a; 技术教程&a…

java学习路程之篇八、知识点、方法介绍、方法的定义和调用格式、方法常见问题、方法重载

文章目录 1、方法介绍2、方法的定义和调用格式3、方法常见问题4、方法重载 1、方法介绍 2、方法的定义和调用格式 3、方法常见问题 4、方法重载

微信小程序三脚猫功夫拿下组件注册与使用

1.局部注册组件 1.1创建components文件夹 1.2创建文件夹MyHeader 1.3选中MyHeader右键 说明&#xff1a;执行此步&#xff0c;将会自己创建四个文件 1.4MyHeader.wxml文件 说明&#xff1a;随便写点h5结构 <view class"sentence">励志语句</view> <…

实验四(双向重发布)7 14

一、配置网络地址&#xff0c;启用OSPF以及环回类型更改&#xff0c;启用RiP&#xff1a; R1&#xff1a; R2&#xff1a; R3&#xff1a; R4&#xff1a;环回接口放置OSPF区域 R5&#xff1a; R6&#xff1a;环回接口放置RIp区域 二、重发布&#xff1a; 未重发布之前&#…

Gitee生成ssh公钥

进入 C:/Users/Administrator/.ssh &#xff0c;没有就手动创建该文件夹 打开cmd输入指令 ssh-keygen -t rsa -C "Gitee SSH Key" -f "C:\Users\.ssh\github_id_rsa"中间通过三次回车键确定查看ssh公钥 type C:\Users\Zzzy\.ssh\github_id_rsa.pub 复制放到…

[LeetCode] #118 杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c; 每个数是它左上方和右上方的数的和。 杨辉三角&#xff1a; class Solution { public:vector<vector<int>> generate(int numRows) {vector<vector<…

Linux 动态主机配置协议 DHCP

文章首发地址 如果管理的计算机有几十台&#xff0c;那么初始化服务器配置IP地址、网关和子网掩码等参数是一个繁琐耗时的过程。如果网络结构要更改&#xff0c;需要重新初始化网络参数&#xff0c;使用动态主机配置协议DHCP&#xff08;Dynamic Host Configuration Protocol&a…

Ubuntu U盘只能读取不能写入

Ubuntu U盘只能读取不能写入 1. 问题2. 解决办法 1. 问题 Ubuntu系统下&#xff0c;U盘突然只能读取无法写入 原因是U盘的文件系统损坏&#xff0c;操作系统为了防止进一步毁坏文件系统&#xff0c;而将其设置成了只读 2. 解决办法 查看U盘分区和挂载 $ df -h 解除挂载 $ u…

微服务系列文章 之 SpringCloud中遇到的一些bug

1、There was a problem with the instance info replicator 错误原因&#xff1a; 该服务尝试将自己作为客服端注册解决办法&#xff1a; 在application.yml配置文件中&#xff0c;设置 # 注册Eureka服务 eureka:client:# Eureka服务注册中心会将自己作为客户端来尝试注册它自…

半导体热阻问题解析(Tc,Ta,Tj,Pc)

自记&#xff1a; 晶体管(或半导体)的热阻与温度、功耗之间的关系为&#xff1a; TaTj-*P(RjcRcsRsa)Tj-P*Rja 公式中&#xff0c;Ta(Ambient temperature)表示环境温度 Tj(Junction temperature)表示晶体管的结温&#xff0c;也就是封装内部半导体裸片的温度。硅片的…

RocketMQ学习笔记(实操篇)

目录 基本操作 启动 测试 双主双从集群搭建 总体架构 工作流程 服务器环境 Host添加信息 防火墙配置 环境变量配置 创建消息存储路径 broker配置文件 修改启动脚本文件 服务启动 查看进程状态 查看日志 mqadmin管理工具 使用方式 命令介绍 集群监控平台搭…

Java使用JNI实现C文件的调用

1.使用IDEA新建工程 构建最基本的maven类型就行&#xff0c;文件结构如下&#xff1a; 其中最主要的类如下&#xff1a; package org.linx;public class TestJNI {static {/*** 加载jni库&#xff0c;有一个重要的点就是生成的为libnative.so&#xff0c;下面加载代码需要消…

241:vue+openlayers绘制多边形,计算面积值

第241个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中绘制多边形,通过Polygon的getArea方法,计算出面积。 直接复制下面的 vue+openlayers示例源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共137行)相关API参考专栏目标示…