谈谈Java中的ThreadLocal

什么是ThreadLocal

  ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内。

跳出误区

  需要重点强调的的是,不要拿ThreadLocal和synchronized做类比,因为这种比较压根就是无意义的!sysnchronized是一种互斥同步机制,是为了保证在多线程环境下对于共享资源的正确访问。而ThreadLocal从本质上讲,无非是提供了一个“线程级”变量作用域,它是一种线程封闭(每个线程独享变量)技术,更直白点讲,ThreadLocal可以理解为将对象的作用范围限制在一个线程上下文中,使得变量的作用域为“线程级”。

  没有ThreadLocal的时候,一个线程在其声明周期内,可能穿过多个层级,多个方法,如果有个对象需要在此线程周期内多次调用,且是跨层级的(线程内共享),通常的做法是通过参数进行传递;而ThreadLocal将变量绑定在线程上,在一个线程周期内,无论“你身处何地”,只需通过其提供的get方法就可轻松获取到对象。极大地提高了对于“线程级变量”的访问便利性。

来看个简单的例子

  假设我们要为每个线程关联一个唯一的序号,在每个线程周期内,我们需要多次访问这个序号,这时我们就可以使用ThreadLocal了.(当然下面这个例子没有完全体现出跨层级跨方法的调用,理解就可以了)

package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by chengxiao on 2016/12/12.
 */
public class ThreadLocalDemo {
    public static void main(String []args){
        for(int i=0;i<5;i++){
            final Thread t = new Thread(){
                @Override
                public void run(){
                    System.out.println("当前线程:"+Thread.currentThread().getName()+",已分配ID:"+ThreadId.get());
                }
            };
            t.start();
        }
    }
    static   class ThreadId{
        //一个递增的序列,使用AtomicInger原子变量保证线程安全
        private static final AtomicInteger nextId = new AtomicInteger(0);
        //线程本地变量,为每个线程关联一个唯一的序号
        private static final ThreadLocal<Integer> threadId =
                new ThreadLocal<Integer>() {
                    @Override
                    protected Integer initialValue() {
                        return nextId.getAndIncrement();//相当于nextId++,由于nextId++这种操作是个复合操作而非原子操作,会有线程安全问题(可能在初始化时就获取到相同的ID,所以使用原子变量
                    }
                };

       //返回当前线程的唯一的序列,如果第一次get,会先调用initialValue,后面看源码就了解了
        public static int get() {
            return threadId.get();
        }
    }
}

执行结果,可以看到每个线程都分配到了一个唯一的ID,同时在此线程范围内的"任何地点",我们都可以通过ThreadId.get()这种方式直接获取。

当前线程:Thread-4,已分配ID:1
当前线程:Thread-0,已分配ID:0
当前线程:Thread-2,已分配ID:3
当前线程:Thread-1,已分配ID:4
当前线程:Thread-3,已分配ID:2 

看看源码

 set操作,为线程绑定变量

 public void set(T value) {
    Thread t = Thread.currentThread();//1.首先获取当前线程对象
        ThreadLocalMap map = getMap(t);//2.获取该线程对象的ThreadLocalMap
        if (map != null)
            map.set(this, value);//如果map不为空,执行set操作,以当前threadLocal对象为key,实际存储对象为value进行set操作
        else
            createMap(t, value);//如果map为空,则为该线程创建ThreadLocalMap
    }

可以看到,ThreadLocal不过是个入口,真正的变量是绑定在线程上的。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;//线程对象持有ThreadLocalMap的引用
}

下面给是Thread类中的定义,每个线程对象都拥有一个ThreadLocalMap对象

    ThreadLocal.ThreadLocalMap threadLocals = null;

现在,我们能看出ThreadLocal的设计思想了:

1.ThreadLocal仅仅是个变量访问的入口;

2.每一个Thread对象都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用;

3.ThreadLocalMap以当前的threadlocal对象为key,以真正的存储对象为value。get时通过threadlocal实例就可以找到绑定在当前线程上的对象。

乍看上去,这种设计确实有些绕。我们完全可以在设计成Map<Thread,T>这种形式,一个线程对应一个存储对象。ThreadLocal这样设计的目的主要有两个:

  一是可以保证当前线程结束时相关对象能尽快被回收;二是ThreadLocalMap中的元素会大大减少,我们都知道map过大更容易造成哈希冲突而导致性能变差。

我们再来看看get方法

 public T get() {
     Thread t = Thread.currentThread();//1.首先获取当前线程
         ThreadLocalMap map = getMap(t);//2.获取线程的map对象
         if (map != null) {//3.如果map不为空,以threadlocal实例为key获取到对应Entry,然后从Entry中取出对象即可。
             ThreadLocalMap.Entry e = map.getEntry(this);
             if (e != null)
                 return (T)e.value;
         }
         return setInitialValue();//如果map为空,也就是第一次没有调用set直接get(或者调用过set,又调用了remove)时,为其设定初始值
     }
setInitialValue
 1  private T setInitialValue() {
 2         T value = initialValue();//获取初始值
 3         Thread t = Thread.currentThread();
 4         ThreadLocalMap map = getMap(t);
 5         if (map != null)
 6             map.set(this, value);
 7         else
 8             createMap(t, value);
 9         return value;
10     }

initialValue方法,默认是null,访问权限是protected,即允许重写。

1 protected T initialValue() {
2         return null;
3     }

谈到这儿,我们应该已经对ThreadLocal的设计目的及设计思想有一定的了解了。

线程独享变量?

  还有一个会引起疑惑的问题,我们说ThreadLocal为每一个线程维护一个独立的变量副本,那么是不是说各个线程之间真正的做到对于对象的“完全自治”而不对其他线程的对象产生影响呢?其实这已经不属于对于ThreadLocal的讨论,而是你出于何种目的去使用ThreadLocal。如果我们为一个线程关联的对象是“完全独享”的,也就是每个线程拥有一整套的新的 栈中的对象引用+堆中的对象,那么这种情况下是真正的彻底的“线程独享变量”,相当于一种深度拷贝,每个线程自己玩自己的,对该对象做任何的操作也不会对别的线程有任何影响。

  另一种更普遍的情况,所谓的独享变量副本,其实也就是每个线程都拥有一个独立的对象引用,而堆中的对象还是线程间共享的,这种情况下,自然还是会涉及到对共享资源的访问操作,依然会有线程不安全的风险。所以说,ThreadLocal无法解决线程安全问题。

  所以,需不需要完全独享变量,进行完全隔离,就取决于你的应用场景了。可以想象,对象过大的时候,如果每个线程都有这么一份“深拷贝”,并发又比较大,对于服务器的压力自然是很大的。像web开发中的servlet,servlet是线程不安全的,一请求一线程,多个线程共享一个servlet对象;而早期的CGI设计中,N个请求就对应N个对象,并发量大了之后性能自然就很差。

  ThreadLocal在spring的事务管理,包括Hibernate的session管理等都有出现,在web开发中,有时会用来管理用户会话 HttpSession,web交互中这种典型的一请求一线程的场景似乎比较适合使用ThreadLocal,但是需要特别注意的是,由于此时session与线程关联,而tomcat这些web服务器多会采用线程池机制,也就是说线程是可复用的,所以在每一次进入的时候都需要重新进行set,或者在结束时及时remove。
时间: 2024-10-06 05:00:12

谈谈Java中的ThreadLocal的相关文章

谈谈java中静态变量与静态方法继承的问题

谈谈java中静态变量与静态方法继承的问题 学习的中如果遇到不明白或者不清楚的的时候,就是自己做些测试,自己去试试,这次我就做一个关于静态变量的继承和静态方法继承问题的测试. 首先我先建一个父类: 这样我在建一个子类: 这些都准备好以后,我对子类创建对象,然后用  类名.静态变量/静态方法  和  对象名.静态方法/静态变量  对他们输出的结果进行测试. 这样输出种类有: 这样我输出的结果是: 这样来总结一下: 1. 通过类名来调用子类中的静态变量和静态方法,当父类与子类相同时是,子类会隐藏父类

谈谈java中成员变量与成员方法继承的问题

谈谈java中成员变量与成员方法继承的问题 关于成员变量和成员方法的的继承问题,我也可以做一个小测试,来看看结果. 首先我们先创建一个父类: 其次再创建一个子类,子类中要比父类中少一个成员方法: 这样我们对子类创建对象,如果不创建对象,我们是无法访问子类的成员变量和成员方法的,因为“无法从静态上下文中引用非静态方法”.我们要输出有: 从以上的数据中,我们就可以得出的输出结果是 这样我们就可以得出结论. 总结: 1.   在结果中可以看出,子类的成员变量和成员方法,与其父类的相同的时候,子类就会覆

谈谈Java引用和Threadlocal的那些事

1 背景某一天在某一个群里面的某个群友突然提出了一个问题:"threadlocal的key是弱引用,那么在threadlocal.get()的时候,发生GC之后,key是否是null?"屏幕前的你可以好好的想想这个问题,在这里我先卖个关子,先讲讲Java中引用和ThreadLocal的那些事. 2 Java中的引用对于很多Java初学者来说,会把引用和对象给搞混淆.下面有一段代码, User zhangsan = new User("zhangsan", 24);

Java中的ThreadLocal详解

一.ThreadLocal简介 多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性.ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题. ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐Threa

谈谈java中的构造函数

本篇博文主要是为新手.对java语言感兴趣的人和那些没有系统学习过java基础知识的人进行一个总结,在文章中对构造函数进行了较为详细的说明和讨论,也包含了我个人对于java面向对象中构造函数的一些看法.希望走在java学习道路上的同行者可以有一个较为清晰的认知和理解.当然仅为个人观点,水平有限,不足之处,还请大家多多指出,互相交流学习. 1.构造函数的概念 很多java新手谈到构造函数就会犯晕,我们先来看看什么是构造函数. 首先,构造函数是函数的一种特殊形式,特殊在哪里?构造函数中不需要定义返回

谈谈java中的WeakReference

java语言中为对象的引用分为了四个级别,分别为 强引用 .软引用.弱引用.虚引用. 本文只针对java中的弱引用进行一些分析,如有出入还请多指正. 在分析弱引用之前,先阐述一个概念:什么是对象可到达和对象不可到达状态. 其实很简单,我举个例子: 现在有如下两个类class A class B,在JVM上生成他们两个类的实例分别为 instance a  instance b 有如下表达式: A a = new A(); B b = new B(); 两个强引用对象就生成了,好吧,那么这个时候我

java中的ThreadLocal

ThreadLocal一般用来保存多个线程对共享变量的修改使得每个线程都能访问自己修改后的变量值.以前我对ThreadLocal的粗略印象就是它是一个map<线程,该线程对共享变量值>,具体是不是这样,怎样实现的并不清楚.遇到概念比较模糊的知识点,只有去搞懂,才能有所进步,所以下面分析一下: 一 应用: //用ThreadLocal声明一个共享的变量 //普通类中的实例变量 1.1 测试带ThreadLocal的共享变量 结果: 1.2测试普通的共享变量 结果: 分析: 在1.1和1.2,分别

谈谈java中的synchronized关键字

1.synchronized的3种用法 public class Client { public static void main(String[] args) { testSynchronized(); } private static void testSynchronized() { new Foo().sayHello(); } static class Foo { //修饰代码块 void sayHello() { synchronized (this) { System.out.pr

理解java中的ThreadLocal(转)

一.对ThreadLocal概术 JDK API 写道: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本.ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联. 二.结合源码理解 可以看到ThreadLocal类中的变量只有这3个int型: private f