ThreadLocal详解

Easter79
• 阅读 780

ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。

  这个玩意有什么用处,或者说为什么要有这么一个东东?先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。

  我们从源码的角度来分析这个问题。

  首先定义一个ThreadLocal:

public class ConnectionUtil {
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    private static Connection initConn = null;
    static {
        try {
            initConn = DriverManager.getConnection("url, name and password");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    public Connection getConn() {
        Connection c = tl.get();
        if(null == c) tl.set(initConn);
        return tl.get();
    }
    
}

  这样子,都是用同一个连接,但是每个连接都是新的,是同一个连接的副本。

  那么实现机制是如何的呢?

  1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

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

  3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。  2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

  4、总结:当我们调用get方法的时候,其实每个当前线程中都有一个ThreadLocal。每次获取或者设置都是对该ThreadLocal进行的操作,是与其他线程分开的。

  5、应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。

  6、其实说再多也不如看一下源码来得清晰。如果要看源码,其中涉及到一个WeakReference和一个Map,这两个地方需要了解下,这两个东西分别是a.Java的弱引用,也就是GC的时候会销毁该引用所包裹(引用)的对象,这个threadLocal作为key可能被销毁,但是只要我们定义成他的类不卸载,tl这个强引用就始终引用着这个ThreadLocal的,永远不会被gc掉。b.和HashMap差不多。

  事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。

举个栗子:

接口定义:

public interface DataBind<T> {
    void put(T t);

    T get();

    void remove();
}

继承父类:

public class ThreadLocalDataBind<T> implements DataBind<T> {
    private final ThreadLocal<T> threadLocal = new ThreadLocal<T>();

    @Override
    public void put(T t) {
        threadLocal.set(t);
    }

    @Override
    public T get() {
        return threadLocal.get();
    }

    @Override
    public void remove() {
        threadLocal.remove();
    }
}

单例定义:

public final class DataBindManager {
    private static final DataBindManager INSTANCE = new DataBindManager();
    private final ConcurrentHashMap<Enum, DataBind> map = new ConcurrentHashMap<Enum, DataBind>();

    private DataBindManager() {
    }

    public static DataBindManager getInstance() {
        return INSTANCE;
    }

    public <T> DataBind<T> getDataBind(Enum bindType) {
        DataBind<T> dataBind = map.get(bindType);
        if (dataBind == null) {
            DataBind bind = new ThreadLocalDataBind();
            dataBind = map.putIfAbsent(bindType, bind);
            if (dataBind == null) {
                dataBind = bind;
            }
        }
        return dataBind;
    }


    public enum DataBindType {
        WEI_XIN_OPEN_ID, //微信账号
        UNION_ID,   //公众平台id
        USER_ID,      //用户uid
        REFER_UID,
        USER_INFO,     //用户信息
        WEI_XIN_PAY_OPEN_ID, //微信支付账号
    }
}

在单例中的局部变量使用,本例子中在拦截器中使用(spring mvc中controller也为ioc管理的singleton单例):

public class AutoLoginInterceptor extends AbstractDetectingUrlInterceptor {
    private final static Logger log = LoggerFactory.getLogger(AutoLoginInterceptor.class);
    private static final DataBind<Long> USER_ID_BIND = DataBindManager.getInstance().getDataBind(DataBindManager.DataBindType.USER_ID);
    private static final DataBind<UserTo> USER_INFO_BIND = DataBindManager.getInstance().getDataBind(DataBindManager.DataBindType.USER_INFO);
    @Autowired
    private WmpUserService wmpUserService;
    @Autowired
    private Contants contants;

    @Override
    protected boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //原有uid
        Long uid = USER_ID_BIND.get();

        String url = request.getRequestURI();
        String e_m = request.getParameter("e_m");
        //短连接手机号自动登录
        if (StringUtils.isNotEmpty(e_m) && !isWxBrower(request) && !isAppBrower(request)) {
            String decryptMob = MobileHelper.getDecryptMobile(e_m);
            if (StringUtils.isEmpty(decryptMob)) {
                log.error("encryptMobile incorrect! deCode = {},{}", decryptMob, url);
                response.sendRedirect("/wmp/tool/error");
                return false;
            }
            String[] arr = decryptMob.split("-");
            UserTo userTo = wmpUserService.isRegisterUser(arr[0], arr[1]);
            if (userTo != null) {
                Long userId = userTo.getUserId();
                if (userId != null && userId > 0) {
                    //覆盖掉原有登录id
                    USER_ID_BIND.put(userId);
                    USER_INFO_BIND.put(userTo);
                    CookieUtil.writeMobileUidCookie(response, "" + userId, contants.mobileHost);
                    log.info("using encryptMobile ! deCode = {},old={},new={},{}", decryptMob, uid, userId, url);
                }
            } else {
                log.error("get userTo is null! deCode = {}", decryptMob);
            }
        }
        return true;
    }

    protected boolean isWxBrower(HttpServletRequest request) {
        Object object = request.getAttribute(CommonContants.KEY_IS_WXBROWER);
        return object != null && object instanceof Boolean && (Boolean) object;
    }

    protected boolean isAppBrower(HttpServletRequest request) {
        Object object = request.getAttribute(CommonContants.KEY_IS_APP);
        return object != null && object instanceof Boolean && (Boolean) object;
    }
}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java ThreadLocal
ThreadLocal是什么定义:提供线程局部变量;一个线程局部变量在多个线程中,分别有独立的值(副本)特点:简单(开箱即用)、快速(无额外开销)、安全(线程安全)场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等场景)ThreadLocal基本API 构
ThreadLocal源码解析及实战应用
ThreadLocal是一个关于创建线程局部变量的类。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。ThreadLocal在设计之初就是为解决并发问题而提供一种方案,每个线程维护一份自己的数据,达到线程隔离的效果。
Tankard825 Tankard825
3年前
最常见的java面试题汇总
1.什么是线程局部变量?(答案)线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如web服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何
Wesley13 Wesley13
3年前
Java ThreadLocal的内存泄漏问题
ThreadLocal提供了线程独有的局部变量,可以在整个线程存活的过程中随时取用,极大地方便了一些逻辑的实现。常见的ThreadLocal用法有:\存储单个线程上下文信息。比如存储id等;\使变量线程安全。变量既然成为了每个线程内部的局部变量,自然就不会存在并发问题了;\减少参数传递。比如做一个trace工具,能够输出工程从开始到结
Wesley13 Wesley13
3年前
Java多线程与并发之ThreadLocal原理解析
1\.ThreadLocal是什么?使用场景ThreadLocal简介ThreadLocal是线程本地变量,可以为多线程的并发问题提供一种解决方式,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,
Wesley13 Wesley13
3年前
Java多线程与并发之ThreadLocal
1\.ThreadLocal是什么?使用场景ThreadLocal简介ThreadLocal是线程本地变量,可以为多线程的并发问题提供一种解决方式,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,
Wesley13 Wesley13
3年前
JAVA基础系列:ThreadLocal
1. 思路1.什么是ThreadLocal?ThreadLocal类顾名思义可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。2.它大致的实现
Easter79 Easter79
3年前
ThreadLocal 详解
概念ThreadLocal用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说ThreadLocal可以为每个线程创建一个【单独的变量副本】,相当于线程的privatestatic类型变量。使用示例publicclassThreadLocalTest{
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k