JAVA ThreadLocal<T>

ThreadLocal:线程本地变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

适用:1 需要各个线程间独立的变量,2 同线程下多方法传递的变量(解决某些方法无法相互传参)

ThreadLocal 适合解决共享/单例对象 的成员变量,在多线程下保持各个线程独立性

2 类的静态ThreadLocal,注意同线程变量覆盖问题

ThreadLocal初始化时引用全局变量/共享变量,则任然是线程共享的,需要同步

4 可以作为线程上下文,方便同线程在多个方法栈中使用,但是要注意跨线程/进程之间丢失与传递

方法中的局部变量,保存在各个访问线程栈中,相互独立不存在线程安全问题,但是注意传入参数/或是方法过程中调用的对象引用和变量是不是多线程共享的

包含如下方法

void set(T value) 设置当前线程的线程局部变量的值。

public T get()     该方法返回当前线程所对应的线程局部变量。

public void remove()  将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected Object initialValue()  返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

 

例如:

class A{
int x=0;
ThreadLocal<integer> y= new ThreadLocal<integer>(){  
	   @Override 
        //通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值   否则会NULL指针错误
        public integer initialValue() {    
            return 0 ;  
        } ;
}
A a= new A()

当A类的对象a作为多个线程的共享变量时,a.x为共享成员变量,线程之间相互竞争,需要lock来保证线程安全。a.y为线程本地变量,每个线程访问a时,y使用的是线程自己的副本,各个线程之间互不影响。ThreadLocal 以Thread.currentThread()为依据获取其对应副本。

实际用例:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

public class ThreadLocal测试 {

	public static void main(String[] args) {
		One one=new One(); // 主线程 和T1线程 共享对象
		one.mapAdd("main", "1");
		one.mapAdd("main1", "2");
		
		Thread t1=new Thread(new RunW(one),"T1");
		t1.start();	
		
		one.showMap();
	}	
}


class One{
	//线程本地变量ThreadLocal
	//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值   否则会NULL指针错误 
	private ThreadLocal<HashMap<String, String>>  map=new ThreadLocal<HashMap<String, String>>() {  
	@Override
        public HashMap<String, String> initialValue() {    
            return new HashMap<String, String>();    
        }    
    };

	public HashMap<String, String> getMap() {

		return map.get();
	}

	public void mapAdd(String K,String V) {
		this.map.get().put(K, V);
	}

	public void showMap() {
		HashMap<String, String> lMap=this.map.get();
		Set set=lMap.entrySet();
		Iterator<Entry<String, String>> iterator=  set.iterator();
		while (iterator.hasNext()) {
			System.out.println(Thread.currentThread().getName()+" "+iterator.next()  );			
		}
	}
}

class RunW implements Runnable{   
	private One one;
	
	public RunW(One one) {
	super();
	this.one = one;
    }

	@Override
	public void run() {
		one.mapAdd("T1", "T1");
		one.mapAdd("T11", "T2");		
		one.showMap();		
	}
	
}

每个线程中可以持有很多个ThreadLocal对象,这些对象通过hash后存储在Thread的ThreadLocalMap中,其中的Key为ThreadLocal对象,

 

注意若将ThreadLocal对象以static修饰时,同一线程访问此类不同对象时,ThreadLocal会被覆盖

public class ThreadLocal测试2 {
	public static void main(String[] args) throws InterruptedException {
		
		WorkTest w1=new WorkTest("1");
		WorkTest w2=new WorkTest("2");
		
		w2.set();
		w1.set();	//w1.set() 会覆盖	w2.set() 的ThreadLocal<String>的值
		w2.get();   
		w1.get();
	}
}

class WorkTest {

	private static ThreadLocal<String> tl=new ThreadLocal<String>() {	
		@Override
		protected String initialValue() {return null;};
	};
	
	private String name;
		
	public WorkTest(String name) {
		super();
		this.name=name;
	}
	
	public void set() {
		this.tl.set(Thread.currentThread().getName()+name);
	}
	public void get() {		
		System.out.println(Thread.currentThread().getName()+" "+this.tl.get());
	}
	
	
	
}

输出结果:

main main1

main main1

w2.set()的操作被后边的w1.set()覆盖

 

 

 

 

value为该对象在本线程中的一个副本。用图表示如下:

从图中可以看出,ThreadLocal本身并不存储value值,只是作为key在ThreadLocalMap中索引value值

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

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

1

2

3

/* 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中。

1

2

3

4

5

6

7

8

9

10

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中。

  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就已经帮我们做了这个事情。

 

内存泄漏问题

 

ThreadLocal是被ThreadLocalMap以弱引用的方式关联着,因此如果ThreadLocal没有被ThreadLocalMap以外的对象引用,则在下一次GC的时候,ThreadLocal实例就会被回收,那么此时ThreadLocalMap里的一组KV的K就是null了,因此在没有额外操作的情况下,此处的V便不会被外部访问到,而且只要Thread实例一直存在,Thread实例就强引用着ThreadLocalMap,因此ThreadLocalMap就不会被回收,那么这里K为null的V就一直占用着内存

如果不及时remove则只要线程存活则Value一直无法GC。建议在过滤器、拦截器、AOP中使用时,在退出方法中清除ThreadLocal,保证每一次线程调用最终出栈时都能清除ThreadLocal。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值