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

这篇具有很好参考价值的文章主要介绍了【Java 并发编程】Java 线程本地变量 ThreadLocal 详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. ThreadLocal 简介

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

java threadlocal,# Java 并发编程,java,jvm,开发语言

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

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) {
            // map存在时,获取value,getEntry中会判断key是不是为null
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 未获取到值,初始化
        return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                // 当值为null或者key为null时进行处理
                return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    // 清除value,设置为null
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
}

从get方法的一系列逻辑我们可以看出,即使使用线程池,在每次get时也会将key为null的值清除掉。

(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 的数组,用以存放线程可能需要的多个副本变量:

java threadlocal,# Java 并发编程,java,jvm,开发语言
可以看到有个 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();

java threadlocal,# Java 并发编程,java,jvm,开发语言

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

  • 强引用

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

  • 软引用

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

  • 弱引用

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

  • 虚引用

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

4.2 ThreadLocal 的内存泄露问题

(1)什么是内存泄露?

内存泄漏: 是指本应该被 GC 回收的无用对象没有被回收,导致内存空间的浪费,当内存泄露严重时会导致内存溢出。Java 内存泄露的根本原因是:长生命周期的对象持有短生命周期对象的引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被 GC 回收。

内存溢出: 就是我们常说的OOM(OutOfMemoryError)异常,简单理解就是内存不够了,通常发生在程序申请的内存超出了JVM中可用内存的大小,就会抛出OOM异常。在 JVM 内存区域中,除了程序计数器外其他的内存区域都有可能抛出 OOM 异常。

ThreadLocal 很好地解决了线程之间需要数据隔离的问题,同时也引入了另一个问题,在应用程序中通常会使用线程池来管理线程,那么线程的生命周期与应用程序的生命周期基本保持一致,如果线程的数量很多,随着程序的运行,时间的推移,ThreadLocal 类型的变量会越来越多,将会占用非常大的内存空间,从而产生内存泄漏,如果这些对象一直不被释放的话,可能会导致内存溢出

(2)ThreadLocal 的内存泄露分析

java threadlocal,# Java 并发编程,java,jvm,开发语言
从图中可以看出,ThreadLocal 对象存在于堆中,有栈中的强引用指向它,也有 ThreadLocalMap 中 Entry 的 key 的弱引用键指向它。

而随着程序的运行,栈中 ThreadLocal 的强引用会消亡,只剩下弱引用连接着 ThreadLocal 独享,由于 ThreadLocalMap.Entity 中的 key 是弱引用,所以堆中的 ThreadLocal 对象会被回收(只要发生 GC,弱引用对象就会被回收),但是 ThreadLocalMap ⽣命周期和 Thread 是⼀样的,它这时候如果不被回收,就会出现这种情况:ThreadLocalMap 的 key 没了,value 还在,这就会造成了内存泄漏问题(由弱引用引起的内存泄漏)

而对于线程来说,线程的生命周期与应用程序的生命周期基本保持一致,所以一直会存在:Current Thread Refefence -> Thread -> ThreaLocalMap -> Entry -> value -> Object 的强引用,这样 value 所强引用的 Object 对象迟迟得不到回收,就会导致内存泄漏

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

ThreadLocalMap 的设计中已经考虑到这种情况,在 ThreadLocal 的 get()、set()、remove() 的时候都会调用 expungeStaleEntry() 方法清除线程 ThreadLocalMap 里所有 key 为 null 的 value。

综上所述,内存泄漏应该只会存在于线程池数量较大且存储在ThreadLocal中的数据量较大时,但是手动调用 remove() 可以加快内存的释放,所以还是推荐手动调用的。

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

java threadlocal,# Java 并发编程,java,jvm,开发语言
如上图所示,如果我们插入一个 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保存日志信息。文章来源地址https://www.toymoban.com/news/detail-766211.html

到了这里,关于【Java 并发编程】Java 线程本地变量 ThreadLocal 详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java并发编程面试题——线程池

    参考文章: 《Java 并发编程的艺术》 7000 字 + 24 张图带你彻底弄懂线程池 (1) 线程池 (ThreadPool) 是一种用于 管理和复用线程的机制 ,它是在程序启动时就预先创建一定数量的线程,将这些线程放入一个池中,并对它们进行有效的管理和复用,从而在需要执行任务时,可以从

    2024年02月07日
    浏览(37)
  • 【Java 并发编程】一文读懂线程、协程、守护线程

    在 Java 线程的生命周期一文中提到了 就绪状态的线程在获得 CPU 时间片后变为运行中状态 ,否则就会在 可运行状态 或者 阻塞状态 ,那么系统是如何分配线程时间片以及实现线程的调度的呢?下面我们就来讲讲线程的调度策略。 线程调度是指系统为线程分配 CPU 执行时间片

    2023年04月08日
    浏览(44)
  • Java面试_并发编程_线程基础

    进程是正在运行程序的实例, 进程中包含了线程, 每个线程执行不同的任务 不同的进程使用不同的内存空间, 在当前进程下的所有线程可以共享内存空间 线程更轻量, 线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程) 并发是单个

    2024年02月07日
    浏览(40)
  • java并发编程:多线程基础知识介绍

    最初的计算机只能接受一些特定的指令,用户每输入一个指令,计算机就做出一个操作。当用户在思考或者输入时,计算机就在等待。这样效率非常低下,在很多时候,计算机都处在等待状态。 后来有了 批处理操作系统 ,把一系列需要操作的指令写下来,形成一个清单,一次

    2024年02月07日
    浏览(36)
  • 【Java并发编程】线程中断机制(辅以常见案例)

    本文由浅入深介绍了中断机制、中断的常见案例和使用场景。 因为一些原因需要取消原本正在执行的线程。我们举几个栗子: 假设踢足球点球时,A队前4轮中了4个球,B队前4轮只中了2个球,此时胜负已分,第5轮这个点球就不用踢了,此时需要停止A队的线程和B队的线程(共

    2024年02月13日
    浏览(22)
  • Java并发编程学习18-线程池的使用(下)

    上篇介绍了 ThreadPoolExecutor 配置和扩展相关的信息,本篇开始将介绍递归算法的并行化。 还记得我们在《Java并发编程学习11-任务执行演示》中,对页面绘制程序进行一系列改进,这些改进大大地提供了页面绘制的并行性。 我们简单回顾下相关的改进过程: 第一次新增时,页

    2024年02月12日
    浏览(26)
  • Java并发编程学习16-线程池的使用(中)

    上篇分析了在使用任务执行框架时需要注意的各种情况,并简单介绍了如何正确调整线程池大小。 本篇将继续介绍对线程池进行配置与调优的一些方法,详细如下: ThreadPoolExecutor 为 Executors 中的 newCachedThreadPool 、 newFixedThreadPool 和 newScheduledThreadExecutor 等工厂方法返回的 Exe

    2024年02月10日
    浏览(28)
  • Java并发编程学习笔记(一)线程的入门与创建

    认识 程序由指令和数据组成,简单来说,进程可以视为程序的一个实例 大部分程序可以同时运行多个实例进程,例如记事本、画图、浏览器等 少部分程序只能同时运行一个实例进程,例如QQ音乐、网易云音乐等 一个进程可以分为多个线程,线程为最小调度单位,进程则是作

    2024年02月16日
    浏览(35)
  • Java并发编程(三)线程同步 上[synchronized/volatile]

    当使用多个线程来访问同一个数据时,将会导致数据不准确,相互之间产生冲突,非常容易出现线程安全问题,比如多个线程都在操作同一数据,都打算修改商品库存,这样就会导致数据不一致的问题。 所以我们通过线程同步机制来保证线程安全,加入同步锁以避免在该线程没有完成

    2024年02月13日
    浏览(26)
  • 【Java|多线程与高并发】线程池详解

    Java线程池是一种用于管理和重用线程的机制,它可以在需要执行任务时,从线程池中获取线程,执行任务,然后将线程放回池中,以便后续使用。线程池可以有效地管理线程的数量,提高程序的性能和资源利用率。 为什么从线程池里面取线程比直接创建线程快呢? 创建线程是

    2024年02月11日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包