Java---ThreadLocal的用法与理解实现

简介: Java---ThreadLocal的用法与理解实现

java.lang 类 ThreadLocal<T>

我们可以称ThreadLocal为:线程本地变量

官方API是这样介绍的:

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

我们直接new 就可以构造一个 ThreadLocal对象。

它只有4个方法:

T get() 
          返回此线程局部变量的当前线程副本中的值。 
protected  T initialValue() 
          返回此线程局部变量的当前线程的“初始值”。 
 void remove() 
          移除此线程局部变量当前线程的值。 
 void set(T value) 
          将此线程局部变量的当前线程副本中的值设置为指定值。

initialValue这个方法是一个延迟调用方法(可以理解成给初始值),在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

如果程序员希望线程局部变量具有 null 以外的值,则必须为 ThreadLocal 创建子类,并重写此方法。通常将使用匿名内部类完成此操作。

通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值
  private static ThreadLocal t= new ThreadLocal(){
      public Integer initialValue() {
          return 0;
      }
  };

ThreadLocal是这样做到为每一个线程维护变量的:

在ThreadLocal类中有一个Map<Thread,Object>,用于存储每一个线程与线程变量的值,Map中元素的键为线程对象,而值对应线程的变量值。

我们自己就可以写出一个简单的实现版本:

package cn.hncu;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocle {
    private Map<Thread, Object> map = new HashMap<Thread, Object>();
    public Object get(){
        Thread curThread = Thread.currentThread();
        Object value = map.get(curThread);
        return value;
    }
    public void set(Object obj){
        Thread curThread = Thread.currentThread();
        map.put(curThread, obj);
    }
}

现在我们用一个实例来加深对ThreadLocal的理解:

package cn.hncu;
import java.util.Random;
import org.junit.Test;
public class ThreadLocalDemo {
    private static ThreadLocal<Object> t1 = new ThreadLocal<Object>();
    public static Object getValue(){
        Object o = t1.get();
        //不用给key,因为t1内部会自动获取当前线程的thread对象,并以上作为key到它的池中去取obj
        if(o==null){
            System.out.println("空的");
            Random r = new Random();
            o = r.nextInt(1000);
            t1.set(o);
        }
        return o;
    }
    @Test
    public void test(){
        Object obj = getValue();
        Object obj2 = getValue();//第二次去拿,不是空的了
        System.out.println(obj+","+obj2);
        System.out.println(obj==obj2);//true
        A a = new A();
        Object obj3 = a.getValue();
        System.out.println(obj==obj3);//true
        B b = new B();
        final Object obj4 = b.getValue();
        System.out.println(obj3==obj4);//true
        //由上面的例子可以知道,只要是同一个线程,不管是哪个类,从ThreadLocal中get的对象都是同一个
        System.out.println("---====不同的线程====---");
        new Thread() {
            @Override
            public void run() {
                A a = new A();
                Object obj5 = a.getValue();
                System.out.println("obj5:"+obj5);
                B b = new B();
                Object obj6 = b.getValue();
                System.out.println("obj6:"+obj6);
                System.out.println("obj5=obj6:"+(obj5==obj6));
                System.out.println("obj4=obj5:"+(obj4==obj5));
            }
        }.start();
    }
}
class A{
    public Object getValue(){
        Object obj = ThreadLocalDemo.getValue();
        System.out.println("A:"+obj);
        return obj;
    }
}
class B{
    public Object getValue(){
        Object obj = ThreadLocalDemo.getValue();
        System.out.println("B:"+obj);
        return obj;
    }
}

通过上面那个实例我们可以知道:

各个线程中访问的是不同的对象。

通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

看下jdk1.7的部分ThreadLocal类源码:

get()与set()方法:

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
目录
相关文章
|
5月前
|
Java
Java中的equals()与==的区别与用法
【7月更文挑战第28天】
76 12
|
2月前
|
存储 安全 Java
深入理解Java中的FutureTask:用法和原理
【10月更文挑战第28天】`FutureTask` 是 Java 中 `java.util.concurrent` 包下的一个类,实现了 `RunnableFuture` 接口,支持异步计算和结果获取。它可以作为 `Runnable` 被线程执行,同时通过 `Future` 接口获取计算结果。`FutureTask` 可以基于 `Callable` 或 `Runnable` 创建,常用于多线程环境中执行耗时任务,避免阻塞主线程。任务结果可通过 `get` 方法获取,支持阻塞和非阻塞方式。内部使用 AQS 实现同步机制,确保线程安全。
|
3月前
|
Java
Java 正则表达式高级用法
Java 中的正则表达式是强大的文本处理工具,用于搜索、匹配、替换和分割字符串。`java.util.regex` 包提供了 `Pattern` 和 `Matcher` 类来高效处理正则表达式。本文介绍了高级用法,包括使用 `Pattern` 和 `Matcher` 进行匹配、断言(如正向和负向前瞻/后顾)、捕获组与命名组、替换操作、分割字符串、修饰符(如忽略大小写和多行模式)及 Unicode 支持。通过这些功能,可以高效地处理复杂文本数据。
|
3月前
|
存储 Java 数据处理
Java 数组的高级用法
在 Java 中,数组不仅可以存储同类型的数据,还支持多种高级用法,如多维数组(常用于矩阵)、动态创建数组、克隆数组、使用 `java.util.Arrays` 进行排序和搜索、与集合相互转换、增强 for 循环遍历、匿名数组传递以及利用 `Arrays.equals()` 比较数组内容。这些技巧能提升代码的灵活性和可读性,适用于更复杂的数据处理场景。
|
3月前
|
算法 安全 Java
JAVA并发编程系列(12)ThreadLocal就是这么简单|建议收藏
很多人都以为TreadLocal很难很深奥,尤其被问到ThreadLocal数据结构、以及如何发生的内存泄漏问题,候选人容易谈虎色变。 日常大家用这个的很少,甚至很多近10年资深研发人员,都没有用过ThreadLocal。本文由浅入深、并且才有通俗易懂方式全面分析ThreadLocal的应用场景、数据结构、内存泄漏问题。降低大家学习啃骨头的心理压力,希望可以帮助大家彻底掌握并应用这个核心技术到工作当中。
|
3月前
|
安全 Java
Java switch case隐藏用法
在 Java 中,`switch` 语句是一种多分支选择结构,常用于根据变量值执行不同代码块。除基本用法外,它还有多种进阶技巧,如使用字符串(Java 7 开始支持)、多个 `case` 共享代码块、不使用 `break` 实现 “fall-through”、使用枚举类型、使用表达式(Java 12 及以上)、组合条件以及使用标签等。这些技巧使代码更加简洁、清晰且高效。
|
4月前
|
存储 安全 Java
Java 中的 ThreadLocal 变量
【8月更文挑战第22天】
38 4
|
4月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
49 2
|
5月前
|
存储 SQL Java
(七)全面剖析Java并发编程之线程变量副本ThreadLocal原理分析
在之前的文章:彻底理解Java并发编程之Synchronized关键字实现原理剖析中我们曾初次谈到线程安全问题引发的"三要素":多线程、共享资源/临界资源、非原子性操作,简而言之:在同一时刻,多条线程同时对临界资源进行非原子性操作则有可能产生线程安全问题。
|
5月前
|
Java
java中return,break以及continue的用法
java中return,break以及continue的用法
50 10