zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java ThreadLocal

JAVA ThreadLocal
2023-09-11 14:22:55 时间

参考 http://www.cnblogs.com/alphablox/archive/2013/01/20/2869061.html

参考 http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/


一、定义

        ThreadLocal就是对直接在thread中申明一个对象这种行为的封装,让调用者看起来就像静态变量一样。这样可以避免反复的申请和释放空间带来的性能损耗。

        ThreadLocal在其内部定义了一个ThreadLocalMap,每当一个线程调用ThreadLocal的get方法时,它就会去map中寻找以该线程id为key的值,如果没找到,就会调用initialValue() 方法初始化该值并插入map,同时返回该值。

ThreadLocal在JDK中是这么定义的:

This class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).

翻译过来大致是这样的:

        ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。

可以总结为一句话:

        ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

也就是说,ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。

二、代码实战

1、MyThreadLocal.java

public class MyThreadLocal {

	public static final void log(String msg) {
		System.out.println(Thread.currentThread().getId() + ". " + msg);
	}

	public static final ThreadLocal<HashMap<Integer, Integer>> threadLocal = new ThreadLocal<HashMap<Integer, Integer>>() {
		//每一次在新的线程中调用threadLocal.get()时,都会调用到该方法,初始化threadLocalMap中key为当前线程id的值
		protected HashMap<Integer, Integer> initialValue() {
			log("threadLocal_initialValue");
			HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
			map.put(-1, -1);
			return map;
		};
	};

	public class MyRunnable implements Runnable {
		int num;

		public MyRunnable(int num) {
			this.num = num;
		}

		@Override
		public void run() {
			HashMap<Integer, Integer> map = threadLocal.get();
			log("before run " + map.toString());
			int value;
			for (int i = 0; i < 5; i++) {
				value = num * 10 + i;
				map.put(i, value);
				log("map_put (" + i + "," + value + ")");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			log("after_put " + map.toString());
			
			HashMap<Integer, Integer> map1 = new HashMap<Integer, Integer>();
			map1.put(100, 100);
			//将新的map设置为threadLocalMap中key为当前线程id的值,下次通过get(),取的也是当前线程id的值
			threadLocal.set(map1);
			log("threadLocal_set " + threadLocal.get().toString());
			
			log("before threadLocal_remove ");
			//调用threadlocal.remove方法后,会执行initialValue方法,重新初始化map
			threadLocal.remove();
			HashMap<Integer, Integer> map2 = threadLocal.get();
			log("after threadLocal_remove " + (map2 == null ? "map = null" : map2.toString()));
		}
	}


	public MyThreadLocal() {
		log("MyThreadLocal init");
	}
	
	class ClassA{
		public String name;
		public ClassA() {
			log("ClassA init");
		}
	}
	
	public static final ThreadLocal<MyThreadLocal> threadLocalTest1 = new ThreadLocal<MyThreadLocal>();
	public static final ThreadLocal<ClassA> threadLocalTest2 = new ThreadLocal<ClassA>();
	
	public void run() {
		log("run");
		for (int i = 0; i < 3; i++) {
			// 注意:这里如果只是执行 new MyRunnable(i).run();方法,那么运行环境依然是主线程
			new Thread(new MyRunnable(i)).start();
		}
	}
}

2、执行方法

	public static void main(String[] args) {
		//threadlocat.get()获取的值也有可能为null
		MyThreadLocal mThreadLocal = MyThreadLocal.threadLocalTest1.get();//null
		ClassA mClassA = MyThreadLocal.threadLocalTest2.get();//null
		System.out.println("localThreadTest<MyThreadLocal>.get() " + (mThreadLocal == null?"is null":"not null"));
		System.out.println("localThreadTest<ClassA>.get() " + (mClassA == null?"is null":"not null"));
		
		mThreadLocal = new MyThreadLocal();
		MyThreadLocal.log("main_thread");
		mThreadLocal.run();
	}

三、运行截图