【Java 并发编程】Java 线程本地变量 ThreadLocal 详解

news2024/9/21 22:43:31

Java 线程本地变量 ThreadLocal 详解

  • 1. ThreadLocal 简介
  • 2. ThreadLocal 的使用
    • 2.1 ThreadLocal 接口
      • (1)initialValue()
      • (2)get()
      • (3)set(T value)
      • (4)remove()
    • 2.2 ThreadLocal 应用
  • 3. ThreadLocal 的实现原理
    • 3.1 ThreadLocal 的原理是什么?
    • 3.2 为什么用 ThreadLocal 做 key?
  • 4. ThreadLocal 引发的内存泄漏
    • 4.1 引用、强引用、软引用、弱引用、虚引用
    • 4.2 ThreadLocal 的内存泄露问题
      • (1)什么是内存泄露?
      • (2)ThreadLocal 的内存泄露分析
      • (3)如何解决内存泄露问题?
      • (4)为什么 key 还要设计成弱引用而不是强引用?
      • (5)ThreadLocal 为什么建议用 static 修饰?
      • (6)总结
  • 5. ThreadLocalMap
    • 5.1 ThreadLocalMap 的基本结构
    • 5.2 ThreadLocalMap 是怎么解决 Hash 冲突的?
    • 5.3 ThreadLocalMap 是怎么扩容的?
  • 6. 父子线程如何共享数据?
  • 7. ThreadLocal 有哪些用途?

1. ThreadLocal 简介

先一起看一下 ThreadLocal 类的官方解释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

用大白话翻译过来,大体的意思是:

ThreadLoal 提供给了线程局部变量。同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。

  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于:每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被 private static 修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

2. ThreadLocal 的使用

2.1 ThreadLocal 接口

ThreadLocal 类接口很简单,只有 4 个方法:initialValue()、get()、set(T)、remove()。

(1)initialValue()

 protected T initialValue() {
     return null;
 }

该方法是一个 protected 的方法,显然是为了让子类重写而设计的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第一次调用 get() 时才执行,并且仅执行1次(即:线程第一次使用 get() 方法访问变量的时候才执行。如果线程先于 get() 方法调用 set(T) 方法,则不会在线程中再调用 initialValue() 方法)。ThreadLocal 中的缺省实现直接返回一个null。

(2)get()

该方法返回当前线程所对应的线程局部变量。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

(3)set(T value)

设置当前线程的线程局部变量的值。

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

(4)remove()

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

该方法是 JDK 5.0 新增的方法:将当前线程局部变量的值删除,目的是为了减少内存的占用。

需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度

使用 ThreadLocal 到底需不需要 remove?

2.2 ThreadLocal 应用

  1. 存储用户信息上下文

使用 ThreadLocal,在控制层拦截请求把用户信息存入 ThreadLocal,这样我们在任何一个地方,都可以取出 ThreadLocal 中存的用户数据。

  1. 数据库连接池

数据库连接池的连接交给 ThreadLoca 进行管理,保证当前线程的操作都是同一个 Connnection。

3. ThreadLocal 的实现原理

3.1 ThreadLocal 的原理是什么?

ThreadLocal 的 get()、set() 实现大同小异,我们以 set() 为例看一下 ThreadLocal.set(T) 方法:

public void set(T value) {
	// 获取当前线程
    Thread t = Thread.currentThread();
    // 调用 getMap() 方法获取对应的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocalMap 是一个声明在 ThreadLocal 的静态内部类,Thread 类中定义了一个类型为ThreadLocal.ThreadLocalMap 的成员变量 threadLocals,也就是 ThreadLocalMap 实例化是在 Thread 内部,所以 getMap() 是直接返回 Thread.threadLocals 的这个成员变量。

public class Thread implements Runnable {
   // ThreadLocal.ThreadLocalMap 是 Thread 的成员变量
   ThreadLocal.ThreadLocalMap threadLocals = null;
}

看下 ThreadLocal 的内部类 ThreadLocalMap 源码,这里其实是个标准的 Map 实现(Map 的本质是一个个 <key,value> 形式的节点组成的数组),内部有一个元素类型为 Entry 的数组,用以存放线程可能需要的多个副本变量:

在这里插入图片描述
可以看到有个 Entry 内部静态类,它继承了 WeakReference,实际上 key 并不是 ThreadLocal 本身,而是它的一个弱引用,可以看到 Entry 的 key 继承了 WeakReference(弱引用),再来看一下 key 怎么赋值的:

public WeakReference(T referent) {
    super(referent);
}

key的赋值,使用的是WeakReference的赋值。

总结

  • Thread 类有一个类型为 ThreadLocal.ThreadLocalMap 的成员变量 threadLocals,每个线程都有一个属于自己的 ThreadLocalMap。

  • ThreadLocalMap 内部维护着 Entry 数组,每个 Entry 代表一个完整的对象,key 是 ThreadLocal 的弱引用,value 是 ThreadLocal 的泛型值。

  • 每个线程在往 ThreadLocal 里设置值的时候,都是往自己的 ThreadLocalMap 里存,读也是以某个 ThreadLocal 作为引用,在自己的 map 里找对应的 key,从而实现了线程隔离。

  • ThreadLocal 本身不存储值,它只是作为一个 key 来让线程往 ThreadLocalMap 里存取值。

3.2 为什么用 ThreadLocal 做 key?

ThreadLocalMap 为什么要用 ThreadLocal 做 key,而不是用 Thread 做 key?

理论上是可以在 ThreadLocal 下定义 Map,key 是 Thread,value 是 set 进去的值,但没那么优雅。这个做法实际上就是所有的线程都访问 ThreadLocal 的 Map,而 key 是当前线程。

但这有点小问题,一个线程是可以拥有多个私有变量的,那 key 如果是当前线程的话,意味着还需要做点「手脚」来唯一标识 set 进去的 value。

假如上一步解决了,还是有个问题:并发足够大时,意味着所有的线程都去操作同一个 Map,Map 体积就很有可能会膨胀,导致访问性能下降。这个 Map 维护着所有线程的私有变量,意味着你不知道什么时候可以「销毁」。

现在 JDK 实现的结构就不一样了,线程需要多个私有变量,那有多个 ThreadLocal 对象就足够了,对应的 Map 体积不会太大。只要线程销毁了,ThreadLocalMap 也会被销毁。

4. ThreadLocal 引发的内存泄漏

4.1 引用、强引用、软引用、弱引用、虚引用

  • 引用

在JVM中,栈内存线程私有,存储了对象的引用,堆内存线程共享,存储了对象实例。

Object o = new Object();

在这里插入图片描述

这个 o,我们可以称之为对象引用,而 new Object() 我们可以称之为在内存中产生了一个对象实例。当写下 o=null 时,只是表示 o 不再指向堆中 object 的对象实例,不代表这个对象实例不存在了。

  • 强引用

强引用是最常见的,只要把一个对象赋给一个引用变量,这个引用变量就是一个强引用,类似 “Object obj=new Object()” 这类的引用,只要对象没有被置 null,在 GC 时就不会被回收。

  • 软引用

软引用相对弱化了一些,需要继承 SoftReference 实现。如果内存充足,只有软引用指向的对象不会被回收。如果内存不足了,只有软引用指向的对象就会被回收。

  • 弱引用

弱引用又更弱了一些,需要继承 WeakReference 实现。只要发生GC,只有弱引用指向的对象就会被回收。

  • 虚引用

最后就是虚引用,需要继承 PhantomReference 实现。它是最弱的一种引用关系。一个对象实例是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象实例被收集器回收时收到一个系统通知。

4.2 ThreadLocal 的内存泄露问题

(1)什么是内存泄露?

意思就是:你申请完内存后,你用完了但没有释放掉,你自己没法用,系统又没法回收。

(2)ThreadLocal 的内存泄露分析

每个 Thread 维护一个 ThreadLocalMap,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object,也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。

ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

使用了 ThreadLocal 后,引用链如图所示:

在这里插入图片描述
ThreadLocal 在没有外部强引用时(把 Threadlocal 变量置为 null 以后,没有任何强引用指向 Threadlocal 实例),发生 GC 时会被回收,但 ThreadLocalMap 的生命周期是跟 Thread 一样的,如果没有手动删除 key ,且 ThreadLocal 的线程一直持续运行,那么这个 Entry 对象中的 value 就有可能一直得不到回收,发生内存泄露

ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会调用 expungeStaleEntry() 清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

ThreadLocal–内存泄漏问题及Java的对应处理办法

(3)如何解决内存泄露问题?

很简单,使用完 ThreadLocal 后,及时调用 remove() 方法释放内存空间。

ThreadLocal<String> localVariable = new ThreadLocal();
try {
    localVariable.set("张三”);
    ……
} finally {
    localVariable.remove();
}

(4)为什么 key 还要设计成弱引用而不是强引用?

key 使用强引用:即便我们在代码中显式的对 ThreadLocal 对象实例的引用置为 null,告诉 GC 要垃圾回收该对象。但是 ThreadLocalMap 还持有这个 ThreadLocal 对象实例的强引用,如果没有手动删除, ThreadLocal 的对象实例不会被回收,导致 Entry 内存泄漏。

而 key 设置为弱引用,GC 扫描到时, 发现 ThreadLocal 没有强引用, 会回收该 ThreadLocal 对象,可以预防大多数内存泄漏的情况。

(5)ThreadLocal 为什么建议用 static 修饰?

首先明确一下 static 修饰的变量的生命周期:static 修饰的变量是在类在加载时就分配地址了,在类卸载才会被回收。

ThreadLocal 的原理是在 Thread 内部有一个 ThreadLocalMap 的集合对象,它的 key 是 ThreadLocal,value 就是你要存储的变量副本,不同的线程他的 ThreadLocalMap 是隔离开的,如果变量 ThreadLocal 是非 static 的就会造成每次生成实例都要生成不同的 ThreadLocal 对象,虽然这样程序不会有什么异常,但是会浪费内存资源,造成内存泄漏。

所以建议 ThreadLocal 用 static 修饰。

(6)总结

  • JVM 利用设置 ThreadLocalMap 的 Key 为弱引用,来避免内存泄露。

  • JVM 利用调用 remove、get、set 方法的时候,回收弱引用。

  • 当 ThreadLocal 存储很多 Key 为 null 的 Entry 的时候,而不再去调用 remove、get、set 方法,那么将导致内存泄漏。

  • 使用 线程池+ ThreadLocal 时要小心,因为这种情况下,线程是一直在不断的重复运行的,从而也就造成了 value 可能造成累积的情况。

5. ThreadLocalMap

5.1 ThreadLocalMap 的基本结构

ThreadLocalMap 虽然被叫做 map,但它没有实现 map 接口。ThreadLocalMap 的数据结构实际上是数组,对比 HashMap 它只有散列数组没有链表,主要关注的是两个要素:元素数组散列方法

  • 元素数组

一个 table 数组,存储 Entry 类型的元素,Entry 是 ThreaLocal 的弱引用作为 key,Object 作为 value 的结构。

private Entry[] table;
  • 散列方法

散列方法就是怎么把对应的key映射到table数组的相应下标,ThreadLocalMap 用的是哈希取余法,取出 key 的 threadLocalHashCode,然后和 table 数组长度减一&运算(相当于取余)。

int i = key.threadLocalHashCode & (table.length - 1);

每创建一个 ThreadLocal 对象,threadLocalHashCode 就会新增 0x61c88647,这个值很特殊,它是斐波那契数也叫黄金分割数hash 增量为 这个数字,带来的好处就是 hash 分布非常均匀。

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

5.2 ThreadLocalMap 是怎么解决 Hash 冲突的?

HashMap 使用了链表来解决冲突,也就是所谓的链地址法(这种方法的基本思想是将所有哈希地址为 i 的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第 i 个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。为了避免 hash 洪水攻击,1.8 版本开始还引入了红黑树)

ThreadLocalMap 没有使用链表,自然也不是用链地址法来解决冲突了,它用的是另外一种方式 - 开放定址法(出现冲突后依次向后查找一个空位置存放)。

在这里插入图片描述
如上图所示,如果我们插入一个 value = 27 的数据,通过 hash 计算后应该落入第 4 个槽位中,而槽位 4 已经有了 Entry 数据,而且 Entry 数据的 key 和当前不相等。此时就会线性向后查找,一直找到 Entry 为 null 的槽位才会停止查找,把元素放到空的槽中。

在 get 的时候,也会根据 ThreadLocal 对象的 hash 值,定位到 table 中的位置,然后判断该槽位 Entry 对象中的 key 是否和 get 的 key 一致,如果不一致,就判断下一个位置。

5.3 ThreadLocalMap 是怎么扩容的?

在 ThreadLocalMap.set() 方法的最后,如果执行完启发式清理工作后,未清理到任何数据,且当前散列数组中 Entry 的数量已经达到了列表的扩容阈值 (len*2/3),就开始执行 rehash() 逻辑:

if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();

再着看 rehash() 具体实现:这里会先去清理过期的 Entry,然后还要根据条件判断 size >= threshold - threshold / 4 也就是 size >= threshold* 3/4 来决定是否需要扩容。

private void rehash() {
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}

private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null)
            expungeStaleEntry(j);
    }
}

接着看看具体的 resize() 方法,扩容后的 newTab 的大小为老数组的两倍,然后遍历老的 table 数组,散列方法重新计算位置,开放地址解决冲突,然后放到新的 newTab,遍历完成之后,oldTab 中所有的 entry 数据都已经放入到 newTab 中了,然后 table 引用指向 newTab

private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    setThreshold(newLen);
    size = count;
    table = newTab;
}

6. 父子线程如何共享数据?

前面介绍的 ThreadLocal 都是在一个线程中保存和获取数据的。

但在实际工作中,有可能是在父子线程中共享数据的。即在父线程中往 ThreadLocal 设置了值,在子线程中能够获取到。

例如:

public class ThreadLocalTest {
 
    public static void main(String[] args) {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        threadLocal.set(6);
        System.out.println("父线程获取数据:" + threadLocal.get());
 
        new Thread(() -> {
            System.out.println("子线程获取数据:" + threadLocal.get());
        }).start();
    }
}

执行结果:

父线程获取数据:6
子线程获取数据:null

你会发现,在这种情况下使用 ThreadLocal 是行不通的。main 方法是在主线程中执行的,相当于父线程。在 main 方法中开启了另外一个线程,相当于子线程。

显然通过 ThreadLocal,无法在父子线程中共享数据。

那么,该怎么办呢?

答:使用 InheritableThreadLocal,它是 JDK 自带的类,继承了 ThreadLocal 类。

修改代码之后:

public class ThreadLocalTest {
 
    public static void main(String[] args) {
        InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
        threadLocal.set(6);
        System.out.println("父线程获取数据:" + threadLocal.get());
 
        new Thread(() -> {
            System.out.println("子线程获取数据:" + threadLocal.get());
        }).start();
    }
}

执行结果:

父线程获取数据:6
子线程获取数据:6

果然,在换成 InheritableThreadLocal 之后,在子线程中能够正常获取父线程中设置的值。

其实,在 Thread 类中除了成员变量 threadLocals 之外,还有另一个成员变量:inheritableThreadLocals。

Thread 类的部分代码如下:

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

最关键的一点是,在它的 init 方法中会将父线程中往 ThreadLocal 设置的值,拷贝一份到子线程中。

7. ThreadLocal 有哪些用途?

  1. 在spring事务中,保证一个线程下,一个事务的多个操作拿到的是一个Connection。

  2. 在hiberate中管理session。

  3. 在JDK8之前,为了解决SimpleDateFormat的线程安全问题。

  4. 获取当前登录用户上下文。

  5. 临时保存权限数据。

  6. 使用MDC保存日志信息。

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

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

相关文章

springboot链接redis (Windows版本)

1。 下载链接 https://redis.io/download/ 2.下载完成之后傻瓜安装 3. 打开下载安装路径 3.输入cmd回车 4.依次执行以下命令 redis-server.exe redis-server.exe redis.windows.conf redis-cli shutdown exit redis-server.exe redis.windows.conf 可成功启动redis 5…

(五)Kubernetes - 手动部署(二进制方式安装)

Kubernetes - 手动部署 [ 4 ] 1 增加Master节点(高可用架构)1.1 部署Master2 Node1.1.1 安装Docker(Master1)1.1.2 启动Docker、设置开机自启(Master2)1.1.3 创建etcd证书目录(Master2)1.1.4 拷贝文件(Master1)1.1.5 删除证书(Master2)1.1.6 修改配置文件和主机名(Master2)1.1.…

在线一键解jsjiami.v6

在当今的互联网时代&#xff0c;JavaScript是web开发的核心技术之一。但是&#xff0c;为了保护JavaScript代码的安全性&#xff0c;很多开发者会使用JS加密技术。其中一个常用的JS加密工具是jsjiami.v6。 JS加密通过对JavaScript代码进行混淆、压缩、编码等多种操作&#xff…

使用crond定时跑脚本备份数据库

前言&#xff1a; 开发环境 服务器&#xff1a;centos 7&#xff08;腾讯云轻量服务器&#xff09; 数据库&#xff1a;mysql 5.7 一、crond是什么&#xff1f; crond 是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程&#xff0c;与windows下的计划任务…

车载测试-can报文解析规则实例

报文解析 报文组成 一般报文主要有以下几个参数&#xff08;比较全的情况下&#xff09; 例 解析报文时主要用到的是帧ID和帧数据 帧ID 接收到的帧ID是十六进制的形式&#xff0c;由29位标识符转换的&#xff0c;目前大多数的通信协议中都直接给出了相应的帧ID&#xff0c…

一文读懂ChatGPT(全文由ChatGPT撰写)

最近ChatGPT爆火&#xff0c;相信大家或多或少都听说过ChatGPT。到底ChatGPT是什么&#xff1f;有什么优缺点呢&#xff1f; 今天就由ChatGPT自己来给大家答疑解惑~ 全文文案来自ChatGPT&#xff01; 01 ChatGPT是什么 ChatGPT是一种基于人工智能技术的自然语言处理系统&…

光耦继电器和普通继电器的区别概述

光耦继电器和普通继电器都是电气传感器元件中的一种&#xff0c;其都能够将电能转化成机械能&#xff0c;并实现各种开关控制和保护控制。但光耦继电器与普通继电器最大的区别在于其输入电路与输出电路之间通过光电转换器件&#xff0c;而不是实现物理接触。本文将从结构、工作…

为什么越来越多的设计师开始用云渲染来渲图?

为什么越来越多的设计师开始使用 云渲染&#xff1f;小编认为可以从设计师以及云渲染平台自身这2个方向分析&#xff0c;下面一起阅读云渲染干货~ 一.针对于设计师 1.出图多&#xff0c;电脑供不应求 绘图员制作完后需要渲染给甲方确认&#xff0c;甲方要求多的又着急的话边改…

MySQL---基本查询DQL(下)(排序查询、聚合查询、分组查询、分页查询、正则表达式)

1. 排序查询 语法&#xff1a; select 字段名1&#xff0c;字段名2&#xff0c;…… from 表名 order by 字段名1 [asc|desc]&#xff0c;字段名2[asc|desc]……注意&#xff1a;asc代表升序&#xff0c;desc代表降序&#xff0c;如果不写默认升序 order by用于子句中可以支持…

CM211-1-ZG_S905L 3-B_当贝纯净桌面-线刷固件包

CM211-1-ZG_S905L 3-B_当贝纯净桌面-线刷固件包-内有教程及短接点 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&…

python中函数与类 类中的方法-静态方法/动态方法

class student():position即令def __init__(self,name,age):self.namenameself.ageagedef eat(self):passclassmethoddef cla(cls):passstaticmethoddef sta():passpassstustudent(name张三,age12) print(stu.position)stu.sta() stu.cla()# 直接使用静态和类方法 student.cla(…

第一章 程序设计基本方法

文章目录 第一章 程序设计基本方法1 程序设计语言1.1 程序设计语言1.2 编译和解释1.3 计算机编程1、为什么学习编程&#xff1f;2、编程 2 Python语言概述2.1 Python语言的发展2.2 Python最小程序 3 Python开发环境配置3.1 Python开发环境安装1、打开官网下载2、运行安装包 3.2…

CompletableFuture使用教学

CompletableFuture使用教学 一、开始一个线程异步执行不需要返回值 通过runAsync方式 //1.不加线程池方式 CompletableFuture<Void> completableFuture CompletableFuture.runAsync(() -> {System.out.println(Thread.currentThread().getName());//停顿几秒try {…

鸿蒙Hi3861学习七-Huawei LiteOS-M(信号量)

一、简介 信号量&#xff08;Semaphore&#xff09;是一种实现任务间通信的机制&#xff0c;实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中&#xff0c;各任务之间需要同步或互斥实现临界资源的保护&#xff0c;信号量功…

企业官方网站怎么申请?

在数字化时代&#xff0c;企业官方网站是展示企业形象、宣传产品和服务的重要窗口。那么&#xff0c;企业官方网站怎么申请呢&#xff1f;下面是一些简单的步骤。 1、选择合适的网站建设平台 目前市面上有许多网站建设平台&#xff0c;企业需要根据自己的需求和预算选择适合自…

Vue3学习笔记(尚硅谷)

文章目录 一、创建vue3工程1-1、使用vite创建vue3项目1-1、安装开发者工具 二、常用Composition API2-1、setup2-2、ref函数2-3、reactive函数2-4、Vue3的响应式原理2-4-1.Vue2的响应式原理2-4-3.Vue3的响应式原理 2-5、reactive对比ref2-6、setup的两个注意点2-7、计算属性与监…

Excel中创建图表的快捷方式哪些

如果你在Excel中创建了很多图表&#xff0c;你可能正在寻找加快创建和格式化速度的快捷方式。以下是一些可以用于Excel图表的有用快捷方式。 一、在新工作表上创建新图表 要在新工作表上创建新图表&#xff0c;请执行以下操作&#xff1a; ​选择要用于创建图表的数据。按F1…

域适应 Domain adaption(1)

一、定义 1、无监督域自适应 经典机器学习假设训练集和测试集来自相同的分布。 然而&#xff0c;这个假设在现实世界的应用程序中可能并不总是成立&#xff0c;例如&#xff0c;数据来源不同。 这种情况下&#xff0c;域分布之间会存在差异&#xff0c;直接将训练好的模型应…

实时数仓项目开发过程中发现的几个问题和优化点(数据接入)

1、属性值被截断的问题 在数据实时接入阶段&#xff0c;使用NIFI ExecuteScript组件生成增、改、删SQL语句&#xff0c;将SQL语句放到了attribute中(详见视频教程http://mp.weixin.qq.com/s?__bizMzIyNzkwNDE4Nw&mid2247486672&idx1&sn41793a61dc5f7ca6b6f9a34b4…

供应链管理系统软件有哪些?这几款软件很不错

一、供应链管理系统解决什么问题 企业不断引进各类管理理念&#xff0c;落地运用各种信息化系统&#xff0c;然而依旧问题频出&#xff1a; 为什么交付还是常常延期&#xff1f;为什么成本依旧居高不下&#xff1f;为什么质量问题频频发生&#xff1f;为什么库存长期积压&…