Threadlocal

2023/11/08

前言: Threadlocal线程本地变量的例子。

ThreadlocalUtils.java

import java.util.HashMap;
import java.util.Map;

public class ThreadlocalUtils {

    public static ThreadLocal<Map> threadlocal = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            System.out.println("threadlocal初始化,值为空");
            return null;
        }
    };

    public static void setData(String key, Object value) {
        Map map = threadlocal.get();
        if (map == null) {
            map = new HashMap();
            map.put(key, value);
            threadlocal.set(map);
        }

        map.put(key, value);
    }

    public static Object getData(String key) {
        Map map = threadlocal.get();
        if (map != null) {
            return map.get(key);
        }

        return null;
    }

    public static void clear() {
        threadlocal.remove();
    }
}

ThreadlocalUtilsTest.java

import java.util.HashMap;
import java.util.Map;

public class ThreadlocalUtils {

    public static ThreadLocal<Map> threadlocal = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            System.out.println("threadlocal初始化,值为空");
            return null;
        }
    };

    public static void setData(String key, Object value) {
        Map map = threadlocal.get();
        if (map == null) {
            map = new HashMap();
            map.put(key, value);
            threadlocal.set(map);
        }

        map.put(key, value);
    }

    public static Object getData(String key) {
        Map map = threadlocal.get();
        if (map != null) {
            return map.get(key);
        }

        return null;
    }

    public static void clear() {
        threadlocal.remove();
    }
}

===>

当前线程是:thread2
threadlocal初始化,值为空
threadlocal初始化,值为空
当前线程是:thread1
测试共享变量
threadlocal初始化,值为空
当前线程是:thread0
不同一线程内不共享threadlocal
测试共享变量
当前线程是:thread0
同一线程内共享threadlocal
共享变量值为:测试共享变量

main线程与异步线程

public class ThreadLocalTest {

    public static void main(String[] args) {
        ThreadLocalUtli.setTrace("外部变量");
        System.out.println("current thread = " + Thread.currentThread().getName());
        new Thread(
                () -> {
                    System.out.println("current thread = " + Thread.currentThread().getName());
                    new Test1().test1();
                }).start();  // 异步调用
        new Test1().test1(); // 同步调用
        new Test2().test2(); // 同步调用
    }
}

可以进行初始化赋值,如果没有这一步的话,get()方法获取到的就是null

现在的线程大多都为线程池管理,也就是存在线程复用的情况,线程处理完一个请求后,处理下一个请求也许是相同的线程。 如果上一个线程中的本地变量使用完毕后没有清理,下一个请求就会获取到上一个请求中的数据,这样就不安全。因此请求处理完毕之后,最好显式调用下remove()方法清除变量中的数据。 防止下一个请求获取到。

tomcat的min-spare-threads配置,最小工作线程的数量。

使用场景

日期处理

每个线程使用自己的DateFormat,就不存在安全问题了。在线程的整个使用过程中,只需要创建一次,又避免了频繁创建的开销。

public class ThreadlocalDateFormat {

T  static ThreadLocal<DateFormat> dateFormatThreadLocal = 'new ThreadLocal<DateFormat>() {+08 = 00'
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH =mm:ss");
        }
    };

T  public static String date2String(Date date) {+08 = '00'
T      return dateFormatThreadLocal.get().format(date);+08 = '00'
    }

    public static Date string2Date(String str) throws ParseException {
T      return dateFormatThreadLocal.get().parse(str);+08 = '00'
    }
}

随机数

上下文信息

典型用途:提供上下文信息。

一个线程执行用户的请求,执行过程中,很多代码会访问一些共同的信息,例如用户身份信息、数据库连接、当前事务等,它们都是线程执行过程中的全局信息。

使用方法参数进行传递会很繁琐,使用Threadlocal会很方便,可以简化代码,避免传参,类似全局变量。

首次获取到信息,调用set()方法进行设置,然后就可以在代码的任意地方调用get()方法进行获取。

注意

  1. Threadlocal对象一般都定义为static,以便于引用。
  2. 如果在线程池里面设置了ThreadLocal变量,则一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,线程池里核心线程的threadlocals变量会一直持有ThreadLocal变量。
  3. 尽可能关掉线程(如果是在使用线程池的方案中,这恐怕很难做到)。
  4. 由于每个线程都有自己独立的变量,内存占用上会多一些,是一种以空间换时间的做法。通过增加资源来保证所有对象的线程安全。