(九)java并发编程--java.lang.TheadLocal

本文详细介绍了Java中的ThreadLocal类,解释了其如何为每个线程提供独立的变量副本,避免线程间的数据竞争。同时,文章提供了ThreadLocal的使用示例,并深入探讨了其实现原理及可能引发的内存泄漏问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java 文档中对ThreadLocal介绍:https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html

ThreadLocal类作用

从名称中Local为本地的,局部的意思。api文章中这样定义的:

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).
这个类提供线程的局部变量,这些变量不同于普通变量,每个线程都会有这些变量的副本(通过get和set方法).ThreadLocal典型实例:在类中私有
静态成员变量,表示线程状态字段(例如用户ID或者事物ID).

java ThreadLocal使得我们可以创建只能由同一个线程读或者写的变量。因此,即使两个线程执行同一个段代码,并且代码都具有对ThreadLocal变量的 引用,那么这两个线程就不能看到彼此ThreadLocal变量。

ThreadLocal实际上并没有解决共享变量的线程安全问题,它只是把所谓的共享变量变成自己的变量而已。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

创建一个ThreadLocal

private ThreadLocal myThread = new ThreadLocal();

实例化一个ThreadLocal对象,对于每个线程,我们只要初始化ThreadLocal一次即可。即使不同的线程执行相同的代码,每个线程都会有他自己的ThreadLocal实例。

即使两个线程设置不同值在相同的ThreadObject,他们也不会互相看到。

访问一个ThreadLocal

上述一个ThreadLocal已经被创建,我们可以set变量值,储存里面,如下。


myThread.set("A thread local value");

可以读取储存在ThreadLocal中的变量值.

String threadLocalValue = (String) myThread.get();

get()方法返回一个对象,set方法参数为Object.

一般使用的ThreadLocal

我们一般这样创建ThreadLocal,以便我们不需要对value进行类型转换,只要直接调用get(),如下例子.

 private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();

然后我们可以这样存储value和获取value的值.

   myThreadLocal.set("Hello ThreadLocal");
   String threadLocalValue =   myThreadLocal.get();

Initial ThreadLocal Value

在ThreadLocal设置的值,只对当前线程可见.
也可以通过指定初始化值,通过继承和重写initialValue()方法,如下.

 private ThreadLocal myThreadLocal = new ThreadLocal<String>(){
           @Override protected String initialValue(){
               return "this is initial value";
           }
       };

所有的线程都能有相同的初始值,在他们调用get()在set()前.

ThreadLocal实例

package java_threadlocal;

/**
 * Created by fang on 2017/11/30.
 * ThreadLocal 实例.
 */
public class ThreadLocalExample {
    public static class MyRunnable implements Runnable{

        private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
        public void run(){
            threadLocal.set((int)(Math.random() * 1000));

            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){

            }

            System.out.println(threadLocal.get());
        }
    }

    public static void main(String[] args) throws InterruptedException{
        MyRunnable myRunnable = new MyRunnable();

        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

ThreadLocal类中的方法

void set(Object value): 设置当前线程的线程局部变量的值。
public Object get():返回当前线程对应的局部变量。
public void remove(): 将当前线程局部变量值删除,目的为了减少内存的占用.当线程结束后,JVM对线程的局部变量会自动回收,所以显示调用该方法并不是必须的。
protected Object initialValue():返回该线程局部变量的初始值,该方法时projected方法,可以让之类覆盖而设计。
这个方法时延迟调用方法,在线程第一次调用get()或set()时执行,并且只执行一次. ThreadLocal中的缺省实现直接返回null。

ThreadLocal实现原理

在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中的key作为线程对象,而值对应的是线程的变量副本.ThreadLocal是空间换时间的思路解决共享变量竞争问题的。

JDK实现ThreadLocal类的类图如下.
这里写图片描述
可以看一下ThreadLocal的源码.
java.lang.Thread


 public class Thread implements Runnable
 {
   …
    ThreadLocal.ThreadLocalMap threadLocals = null;
   …
 }

java.lang.ThreadLocal

 public class ThreadLocal {
    /************************** SET CASE ****************/
        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);
    }

    /******************** GET CASE ********************/
    public T get() {
         Thread t = Thread.currentThread();
         ThreadLocalMap map = t.threadLocals;
         if (map != null) {
             ThreadLocalMap.Entry e = map.getEntry(this);
             if (e != null)
                 return (T)e.value;
         }
         return setInitialValue();
    }

    private T setInitialValue() {
         T value = initialValue();
         Thread t = Thread.currentThread();
         ThreadLocalMap map = getMap(t);
         if (map != null)
             map.set(this, value);
         else
             createMap(t, value);
         return value;
    }

    protected T initialValue() {
         return null;
    }

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

    ...

    ...
    static class ThreadLocalMap {

       static class Entry extends WeakReference {
          Object value;
          Entry(ThreadLocal k, Object v) {
             super(k);
             value = v;
          }
       }

       private void  set(ThreadLocal key, Object value) {

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

第一次set对象,如果当前线程的ThreadLocalMap不存在,它就会被创建,然后将线程的副本变量设置为Map的value,将ThreadLocal实例作为key。

调用第一次get()的时候,如果当前线程的ThreadLocalMap存在,则使用当前的ThreadLocal实例作为key,取得对应的副本变量。否则为当前线程创建一个新的ThreadLocalMap对象,并设置默认的初始值。

ThreadLocal内存泄漏

https://www.cnblogs.com/onlywujun/p/3524675.html

参考:http://tutorials.jenkov.com/java-concurrency/threadlocal.html

<think>好的,我现在需要帮助用户了解JavaThreadLocal的使用方法及原理。根据用户提供的引用内容,我需要先整理相关信息,并确保回答结构清晰,符合用户的要求。 首先,用户提供的引用中有四个部分,其中引用[1]提到了如何通过覆盖initialValue()方法为ThreadLocal变量指定初始,使用了匿名子类的方式。引用[2]是关于set方法的源码分析,说明ThreadLocal如何存储线程本地变量到ThreadLocalMap中。引用[3]解释了ThreadLocal可能存在的内存泄漏问题,因为Entry对ThreadLocal有强引用。引用[4]提到ThreadLocal变量通常用private static修饰,以及与synchronized的区别。 接下来,我需要将这些信息整合成一个连贯的回答,分步骤说明ThreadLocal的使用方法和原理。同时,根据系统级指令,需要生成相关问题,并正确引用来源。 首先,使用方法部分应该包括初始化方法,比如覆盖initialValue()和使用withInitial,以及set和get方法。原理部分需要解释ThreadLocalMap的结构,每个线程如何持有自己的副本,以及可能的泄漏问题。内存管理部分需要强调remove方法的重要性。 需要注意用户可能对ThreadLocal的工作原理不太熟悉,需要简明扼要地解释。同时,要确保引用标注正确,例如在提到initialValue方法时引用[1],在set方法时引用[2],内存泄漏时引用[3],变量修饰时引用[4]。 可能还需要比较ThreadLocal和synchronized的区别,但用户的问题主要关注使用方法和原理,所以这部分可以简略提及,或放在相关问题里。 最后,生成相关问题时要基于回答内容,比如内存泄漏、与synchronized的区别、正确用法等。 需要检查是否符合格式要求,行内公式用$...$,独立公式用$$...$$,但在这个主题中可能数学公式较少,但如果有的话需要注意格式。另外,确保使用中文回答,语法正确。 现在开始组织内容: 1. 定义ThreadLocal的作用:线程局部变量,线程间隔离,类与方法间共享。 2. 使用方法: a. 初始化:覆盖initialValue(引用[1]),使用withInitial。 b. set和get(引用[2])。 3. 原理: a. ThreadLocalMap结构,每个Thread持有(引用[2]和引用[3])。 b. Entry的弱引用和内存泄漏(引用[3])。 4. 内存管理:remove方法的重要性。 5. 最佳实践:private static修饰(引用[4])。 确保每个引用正确标注,例如在解释initialValue时标注[^1],set方法时标注[^2],内存泄漏标注[^3],变量修饰标注[^4]。 然后生成相关问题,比如内存泄漏如何避免,与synchronized的区别,正确使用场景等。</think>### Java ThreadLocal 使用方法及原理 #### 1. **ThreadLocal 的核心作用** ThreadLocal 提供线程局部变量,每个线程独立访问自己的变量副本,实现线程间数据隔离,同时支持类或方法间的共享。例如,数据库连接或日期格式化工具可借助 ThreadLocal 避免多线程竞争。 #### 2. **使用方法** - **初始化** 通过覆盖 `initialValue()` 方法或使用 `withInitial` 工厂方法: ```java // 匿名子类方式(引用[1]) private static ThreadLocal<String> myThreadLocal = new ThreadLocal<>() { @Override protected String initialValue() { return String.valueOf(System.currentTimeMillis()); } }; // Java 8+ 简化方式 private static ThreadLocal<String> myThreadLocal = ThreadLocal.withInitial(() -> "默认"); ``` - **赋与取** - `set(T value)`:将存储到当前线程的 ThreadLocalMap 中。 - `get()`:从当前线程的副本中读取。 ```java myThreadLocal.set("新"); String value = myThreadLocal.get(); // 返回当前线程的私有 ``` #### 3. **实现原理** - **数据结构** 每个线程(`Thread` 类)内部维护一个 `ThreadLocalMap`,其ThreadLocal 实例(弱引用),为存储的数据。 ```java // Thread 类的成员变量 ThreadLocal.ThreadLocalMap threadLocals = null; ``` - **存储过程** 调用 `set()` 时,数据存入当前线程的 `ThreadLocalMap`。若 Map 未初始化,则先创建(引用[2]): ```java 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` 的 Entry 对 ThreadLocal 是弱引用,但对是强引用。若线程长期运行且未调用 `remove()`,可能导致无法回收(引用[3])。 #### 4. **内存管理最佳实践** - **显式清理** 使用后调用 `remove()` 清除当前线程的,避免内存泄漏: ```java myThreadLocal.remove(); ``` - **变量修饰** ThreadLocal 实例通常声明为 `private static`,以限制作用域并减少实例数量(引用[4])。 #### 5. **与 synchronized 的区别** - **synchronized**:通过锁机制实现线程间数据共享。 - **ThreadLocal**:通过空间换时间,避免数据竞争。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值