Java ThreadLocal

背景:

最近项目中需要调用其他业务系统的服务,使用的是Java的RMI机制,但是在调用过程中中间件发生了Token校验问题。而这个问题的根源是每次用户操作,没有去set Token导致的。这个Token是存储在ThreadLocal变量中的,根据servlet的单例多线程原理,使用一个拦截器每次向Thread中写入这个token完美的解决了这个问题。

ThreadLocal

ThreadLocal是Java lang包里面的一个类,这个类用来提供线程级变量的实现。想要明白ThreadLocal首先应该明白Java线程和Java对象的关系。Java对象就是一堆事物(或比作物料),线程就好比工厂流水线。不同流水线之间是独立的,它们可以使用同一堆物料,也可以使用自己的物料,可以说Java现场和对象是两个维度的东西。

我们在Java中经常讨论的线程同步问题就是不同流水线之间共享物料的问题,如果不对共享的对象加以控制,那么线程就会出现各种奇怪的问题,比如原料为0的情况下还有Thread继续生成产品。

在Java线程中比较容易忽视的问题是线程局部变量,或者说那些对象是线程特有的,不同线程之间独立使用,线程结束后这些对象会被gc回收。我们可以看下面的代码:

public class ThreadLoaclTest {
	public static void main(String[] args) {
		SequenceNumberRandom r = new SequenceNumberRandom();

		Client c1 = new Client(r);
		Client c2 = new Client(r);
		Client c3 = new Client(r);
		Client c4 = new Client(r);

		c1.start();
		c2.start();
		c3.start();
		c4.start();
	}
}

class SequenceNumberRandom {
	private int n = 0;
	private  static  ThreadLocal<Integer> n = new ThreadLocal<Integer>(){
		public Integer initialValue(){
			return 0;
		}
	};

	public int getNextNum() {

		//int m = n.get() + 1;

		//n.set(m);
		//return n.get();

		 return n++;
	}

}

class Client extends Thread {

	private SequenceNumberRandom r;

	public Client(SequenceNumberRandom r) {
		this.r = r;
	}

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + " : "
					+ r.getNextNum());
		}

	}
}

其实上面三种方法殊途同归,指导思想都是为每个线程new一个对象。

上面的程序,我们其实是希望SequenceNumberRandom类的对象能为每个Thread独立的产生连续的序号,就好像每个生产线都独立产生自己的生成序列号,想要实现这个任务。我们当然可以修改上面的程序,构造生成线程的时候独立的去实例化SequenceNumberRandom对象。或者在生成线程内构造SequenceNumberRandom对象。

但是如果我们的需求变了呢?我们想要加一个开关,在某段时间内,一个线程生成的产品ID要一致,不同线程间要不一致。那么这个时候我们会发现,我们其实希望的不是让每个线程都一个自己的对象a,希望独立拥有的只是a里面的一个变量b,并且我们的a如果能是一个单例,那么我们的程序会有更好的扩展性、维护性。

针对这个问题Java为我们提供了ThreadLocal类来实现线程间变量的隔离,我们只需要把对象a的b变量定义为ThreadLocal类型,然后使用get和set方法就可以为每个线程读取值。(注释部分就是ThreadLocal的实现)

用法

ThreadLocal的用法很简单,我们可以把它作为一种“变量类型”来使用,记住它的用途:为每个线程提供一个变量副本。当定义变量的时候用ThreadLocal<T>类代替即可。

当读取变量数据的时候使用get,当设置变量数据的时候用set。当然,既然是变量,那么就应该有初始值,如果我们没有set过变量,那么get的时候会获得null,如果我们不希望初始值是null,可以自己覆盖initalValue方法,通常情况下我们都是下面的这种方式重写initalValue方法:

	// private int n = 0;
	private  static  ThreadLocal<Integer> n = new ThreadLocal<Integer>(){
		public Integer initialValue(){
			return 0;
		}
	};

原理

说起ThreadLocal原理,牢记我们上面的中心思想,为每个Thread 建立一个对象,通读ThreadLocal源码就会发现,它内部有一个ThreadLocalMap,这个Map用来存储变量,key是ThreadLocal类型,它的一个引用放在了Thread类中。

初始化:

ThreadLocal构造方法并没有做事情,它的成员变量大部分是static的,是用来为ThreadLocal对象生成一个唯一HashCode的。

调用get:

调用get方法时,ThreadLocal会调用Thread.currentThread方法获取当前线程对象,然后读取当下线程对象的ThreadLocalMap对象,如果该Map对象为不null就把this作为key,调用getEntry方法获取线程变量。如果为map或者value为null则调用setInitialValue方法。

调用set:

调用set原理和get一样。

注意:

我们需要注意的是,ThreadLocal只是简单的提供了一种线程变量隔离的方法,对于基本数据类型它能很好工作,如果是对象类型,两个线程可能访问同一个对象。

Synchronized和ThreadLocal

现在网上的资料对Synchronized和ThreadLocal的对比分析文章已经很多了,这里总结下作者们的中心思想。

1、二者性质不同:Synchronized是Java关键字,ThreadLocal是一个Java类。

2、二者功效不同:Synchronized是用来同步线程资源的,ThreadLocal是用来隔离线程资源的。

3、实现机制:Synchronized靠的Java锁机制实现,ThreadLocal靠的是给每个线程建立一个Map实现的,线程销毁则这个Map销毁。

总结:

ThreadLocal是用来进行线程变量隔离的,让每个线程都有一个变量副本。ThreadLocal常出现在中间件编程中。我们可以这样用它:

比如用户登录后,每个用户都有一个LoginContext,但是我们不知道哪个业务组件会需要这个LoginContext。

最简单也是最常用的方式是业务组件自己在接口中明确指出自己的方法需要一个LoginContext,但是如果我们的业务组件发生了变量,比如安全校验机制变了,那么我们是需要修改接口的,这会很麻烦,甚至修改扩展到整个系统。

那么上面的一种解决方案就是,我们为用户的操作加入一个拦截器,然后在拦截器中把用户的LoginContext放入一个单例对象的ThreadLocal变量中,业务组件可以从这个单例对象中读取信息。这样就不需要在接口中明确指明需不需要LoginContext了。

当然上面只是举个例子不是一个实际业务场景。见到过的一个实际场景:

背景中提到的,在SSO登录系统中,用户的每步操作都可能需要校验登录时系统产生的Token,这个时候我们把整改Token扩展到系统中是不合适的,所以把token存在一个单例对象中心,token用ThreadLocal存储。

最后,ThreadLoacl如果写中间件时可能会很好用,但是不建议滥用,有些时候明确的指明需要的东西有利于系统的稳定性。

时间: 2024-11-06 17:35:56

Java ThreadLocal的相关文章

关于Java ThreadLocal

转自:http://www.appneta.com/blog/introduction-to-javas-threadlocal-storage/ What is ThreadLocal? A simple example As its name suggests, a single instance of ThreadLocal can store different values for each thread independently. Therefore, the value stor

深入研究JAVA ThreadLocal类

深入研究java.lang.ThreadLocal类 一.概述 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是 threadlocalvariable(线程局部变量).也许把它命名为ThreadLocalVar更加合适.线程局部变量 (ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程

java ThreadLocal解读

Thread.java源码中: ThreadLocal.ThreadLocalMap threadLocals = null; 即:每个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(如下所示),所以Thread类可以进行引用. static class ThreadLocalMap { 所以每个线程都会有一个ThreadLocal.ThreadLocalMap对

Java ThreadLocal 简介

ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean.事务管理.任务调度.AOP等模块都出现了它们的身影,起着举足轻重的作用.要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡垒. 我们知道spring通过各种模板类降低了开发者使用各种数据持久技术的难度.这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突.我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源.但这

理解Java ThreadLocal

ThreadLocal是什么 早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. ThreadLocal很容易让人望文生义,想当然地认为是一个"本地线程".其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些. 当使用ThreadLocal维护变量

JAVA ThreadLocal 对象 ServletActionContext

最近在开发过程中,在做一个字典项服务的时候,最开始采用了ThreadLocal对象来缓存数据.在使用ThreadLocal过程中遇到一些问题,这里和大家分享一下. 一. 什么是ThreadLocal? 顾名思义它是local variable(线程局部变量).它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本.从线程的角度看,就好像每一个线程都完全拥有该变量. 它主要由四个方法组成initialValue(),get(),set(T),remove(),其中initialVal

14、Java并发性和多线程-Java ThreadLocal

以下内容转自http://ifeve.com/java-theadlocal/: Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作.因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域. 1.创建一个ThreadLocal对象 如下所示,创建一个ThreadLocal变量: private ThreadLocal myThreadLocal = n

Java ThreadLocal 深入剖析

最近看Android FrameWork层代码,看到了ThreadLocal这个类,有点儿陌生,就翻了各种相关博客一一拜读:自己随后又研究了一遍源码,发现自己的理解较之前阅读的博文有不同之处,所以决定自己写篇文章说说自己的理解,希望可以起到以下作用: 可以疏通研究结果,加深自己的理解 可以起到抛砖引玉的作用,帮助感兴趣的同学疏通思路 分享学习经历,和大家一起交流和学习 一. ThreadLocal 是什么 ThreadLocal 是Java类库的基础类,在包java.lang下面: 官方的解释是

[Java]ThreadLocal详解

我们知道Spring通过各种模板类降低了开发者使用各种数据持久技术的难度.这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突.我们使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源.但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享.虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题. 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对

Java ThreadLocal 理解

ThreadLocal 概念: ThreadLocal不是用来解决对象共享访问的问题,而主要是提供了保存对象的方法和避免参数传递的方便的对象访问方式. ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量的时候ThreadLocal为每一个使用该变量的线程提供了独立的变量副本,也就是说每一个线程都可以单独改变自己的副本,而不会影响其他线程的副本. 从线程的角度来看目标变量就是当前线程的本地变量,这也就是类名Local的含义. Thread