ThreadLocal能解决线程安全问题?胡扯!本文教你正确的使用姿势【享学Java】(上)

简介: ThreadLocal能解决线程安全问题?胡扯!本文教你正确的使用姿势【享学Java】(上)

前言


ThreadLocal:线程 + 本地 -> 线程本地变量(所以说我觉得它取名叫ThreadLocalVariable获取还更能让人易懂些),它的出镜率可不低。虽然写业务代码一般用不着,但它是开源工具的常客:用于在线程生命周期内传递数据。


有的人说,每看一遍ThreadLocal都会有新的感受,这其实是比较诡异的现象,因为我认为“真理”是不应该经常变的(或者说是不可能变化的)。我自己百度了一波,关于ThreadLocal的文章满天飞,有讲使用的亦有讲原理的,鱼龙混杂。其中有一派文章主旨讲述:使用ThreadLocal解决多线程程序的并发问题,使用该工具写出简洁、优美的多线程程序…


这类水文不在少数,大有占据主流的意思,它对初学者的误导性非常大,从而造成了每看一遍都会有新感受的错觉。本文为社区贡献一份微薄之力,在这里教你完全正确的使用ThreadLocal的姿势,避免你以后再犯迷糊。


正文


本文的内容并不讲述ThreadLocal/InheritableThreadLocal的源码、原理,一方面确实不难,另一方面关于它的源码、原理讲解的相关文章确实不在少数。


ThreadLocal是什么?


我们从字面上的意思来理解ThreadLocal,Thread:线程;Local:本地的,局部的。也就是说,ThreadLocal是线程本地的变量,只要是本线程内都可以使用,线程结束了,那么相应的线程本地变量也就跟随着线程消失了。


ThreadLocal和InheritableThreadLocal均是JDK1.2新增的API,在JDK5后支持到了泛型。它表示线程局部变量:为当前线程绑定一个变量,这样在线程的声明周期内的任何地方均可取出。


说明:InheritableThreadLocal继承自ThreadLocal,在其基础上扩展了能力:不仅可在本线程内获取绑定的变量,在子线程内亦可获取到。(请注意:必须是子线程,若你是线程池就可能不行喽,因为线程池里的线程是实现初始化好的,并不一定是你的子线程~)


它仅有如下三个public方法:


public void set(T value) { ... }
public T get() { ... }
public void remove() { ... }


分别代表:


  • 设置值:把value和当前线程绑定
  • 获取值:获取和当前线程绑定的变量值
  • 删除值:移除绑定关系


说明:虽然每个绑定关系都是使用的WeakReference,但是还是建议你显示的做好remove()移除动作,否则容易造成内存泄漏。当然关于ThreadLocal内存泄漏并不是本文的内容,有兴趣可以自行去了解。


另外对于解释ThreadLocal是什么,建议可参考下它的Javadoc:


 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code 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).


大致意思是:


该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量
(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。
ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程
(例如,用户 ID 或事务 ID)相关联。



更准确的说,一般我们使用ThreadLocal是作为private static final字段来使用的~


ThreadLocal怎么用?


知道了ThreadLocal是什么后,怎么用其实就非常简单了。看如下这个简单示例:

本例模拟使用Person对象和当前线程绑定:


@Setter
@ToString
private static class Person {
    private Integer age = 18;
}


书写测试代码:


private static final ThreadLocal<Person> THREAD_LOCAL = new ThreadLocal<>();
@Test
public void fun1() {
    // 方法入口处,设置一个变量和当前线程绑定
    setData(new Person());
    // 调用其它方法,其它方法内部也能获取到刚放进去的变量
    getAndPrintData();
    System.out.println("======== Finish =========");
}
private void setData(Person person){
    System.out.println("set数据,线程名:" + Thread.currentThread().getName());
    THREAD_LOCAL.set(person);
}
private void getAndPrintData() {
    // 拿到当前线程绑定的一个变量,然后做逻辑(本处只打印)
    Person person = THREAD_LOCAL.get();
    System.out.println("get数据,线程名:" + Thread.currentThread().getName() + ",数据为:" + person);
}


运行程序打印输出:

set数据,线程名:main
get数据,线程名:main,数据为:Test2.Person(age=18)
======== Finish =========



这便是ThreadLocal的典型应用场景:方法调用间传参,并不一定必须得从方法入参处传入进来,还可以通过ThreadLocal来传递,进而在该线程生命周期内任何地方均可获取到,非常的方便有木有。


小细节:set和get数据时的线程是同一个线程:均未main线程


局限性


上例是ThreadLocal的典型应用场景,大部分情况下均能正常work。但是,在当下互联网环境下,经常会用到了异步方式来提高程序运行效率,比如如上方法调用getAndPrintData()因比较耗时所以我希望异步去进行,改造如下:


@Test
public void fun1() throws InterruptedException {
    // 方法入口处,设置一个变量和当前线程绑定
    setData(new Person());
    // getAndPrintData();
    // 异步获取数据
    Thread subThread = new Thread(() -> getAndPrintData());
    subThread.start();
    subThread.join();
  // 非异步方式获:在主线程里获取
  getAndPrintData();
    System.out.println("======== Finish =========");
}


运行程序,打印输出:


set数据,线程名:main
get数据,线程名:Thread-0,数据为:null
get数据,线程名:main,数据为:Test2.Person(age=18)
======== Finish =========


线程名为Thread-0的子线程里并没有获取到数据,只因为它并不是当前线程,貌似合情合理,这便是ThreadLocal的局限性。


那既然这是一个常见需求,除了把变量作为方法入参传进去,有没有什么更为便捷的方案呢?有的,JDK扩展了ThreadLocal提供了一个子类:InheritableThreadLocal,它能够向子线程传递数据。

相关文章
|
10天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
12天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
12天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
13天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
35 3
|
13天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
93 2
|
21天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
46 6
|
29天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
8月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
5月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
75 1
|
6月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
111 0