ThreadLocal 和 Synchonized 共同点在于都能用于解决多线程并发访问,可是 ThreadLocal 与 synchronized 有本质的差别:
synchronized 是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程访问。
ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享,保证了线程间的安全
ThreadLocal在Spring事务管理过程中运用较为常见:
public void serviceMethod(){
Connecion conn = null;
try{
Connection conn = getConnetion();
conn.setAutoCommit(false);
Dao1 dao1 = new Dao1(conn);
dao1.doSomething();
Dao2 dao2 = new Dao2(conn);
dao2.doSomething();
Dao3 dao3 = new Dao3(conn);
dao3.doSomething();
conn.commit();
}
}
Class Dao1{
private Connection conn = null;
public Dao1(Connection conn){
this.conn = conn;
}
public void doSomething(){
PrepareStatement pstmt = null;
try{
pstmst = conn.prepareStatement(sql);
pstmt.execute...
}
}
}
上面的代码中未使用 ThreadLoca,Spring主要解决的是如何让Spring的多个执行阶段中链接同一个数据来源,若是不能保证数据源相同,代码都无法正确运行
要让三个Dao都连接相同数据源,可以给他们都传一个相同的数据库连接:
所以Spring事务通过多线程绑定解决这个问题,因为Dao的每一个请求周期是通过线程来执行,即运用ThreadLocal将数据库中的connection作为独立的副本绑定到每一个线程中,线程自然也可以独立改变自己的副本,也不影响其他线程所对应的副本,可以理解为各线程专有的本地变量
因为不管程序开启事务还是执行具体的 sql 都需要一个具体的数据库连接。
ThreadLocal 思路:
public class HYQ{
public static ThreadLocal<Integer> tlForInteger = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 1;
}
};
public static ThreadLocal<String> tlForString = new TreadLocal<String>(){
@Override
protected String initialValue(){
return "i miss u";
}
};
public static class Tsthread implements Runnable{
@Override
public void run() {
tlForString.set("thread");
System.out.println(threadLocalLong.get());
System.out.println(threadLocalString.get());
}
}
public static void main(String[] args){
tlForInteger.set(100L);
tlForString.set("test");
//打包 Runnable 和start()的方法,这里就不写了
//new Thread(new Runnable() {}).start();
Tsthread XYDD = new Tsthread();
Thread thread = new Thread(XYDD);
thread.start();
System.out.println(threadLocalLong.get());
System.out.println(threadLocalString.get());
}
}
具体实现:
import java.util.HashMap;
import java.util.Map;
public class TaiYuan {
public static void main(String [] args){
ResourceHolder.putResource("conn",new Conn("connection1"));
new Thread(new Runnable() {
@Override
public void run() {
// 该线程不会得到主线程绑定的变量
System.out.println(ResourceHolder.getResource("conn"));
}
}).start();
System.out.println(ResourceHolder.getResource("conn"));
new TaiYuan().function1();
new Taiyuan().function2();
System.out.println(ResourceHolder.getResource("conn"));
}
public void function1(){
System.out.println(ResourceHolder.getResource("conn"));
}
public void function2(){
System.out.println(ResourceHolder.getResource("conn"));
}
}
class ResourceHolder{
public static ThreadLocal<Map<Object,Object>>
threadLocalMap=new ThreadLocal<Map<Object,Object>>();
public static void putResource(Object key,Object value){
if(threadLocalMap.get()==null)
threadLocalMap.set(new HashMap<Object,Object>());
threadLocalMap.get().put(key, value);
}
public static Object getResource(Object key){
if(threadLocalMap.get()==null)
threadLocalMap.set(new HashMap<Object,Object>());
return threadLocalMap.get().get(key);
}
public static void clearResource(Object key,Object value){
if(threadLocalMap.get()!=null)
threadLocalMap.remove();
}
}
class Conn{
private String name;
public Conn(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Conn [name=" + name + "]";
}
}
实现 ThreadLocal 类接口分别有四种方法:
private static ThreadLocal <Integer/String> XYDD = new ThreadLocal <Integer/String> (){};
——XYDD 代表一个能够存放 Integer/String 类型的ThreadLocal 对象
上代码分析 ThreadLocal 的实现过程:
- 先定义一个静态 ThreadLocal 对象
//先初始化ThreadLocal,同时初始化Integer
private static THreadLocal<Integer> XYDD = new ThreadLocal<Integer>(){
//重写初始化方法
@Override
protected Integer initialValue(){
//返回integer随便一个值
Integer integer = 1;
return integer;
}
}
//ThreadLocal对象是可以定义多个的,并且是部疼痛类型的
//上面定义的integer类型的ThreadLocal,这里可以定义一个String的ThreadLocal
//且最终都会被保存在entry[]数组里面
private static ThreadLocal<String> stringThreadLocal;
- 创建一个方法用于开启多个线程用作测试
public void StartThreadArray(){
//创建线程数组,用于下面遍历线程
Thread[] xiaowang = new Thread[3];
for(int i = 0; i < xiaowang.length; i++){
//new一个自定义线程类的对象,传入自定义类里的局部变量的值
TestThread WSQ = new TestThread(i+2);
//创建线程,传入自定义线程类的对象
Thread thread = new Thread(WSQ);
//遍历把多线程赋值给线程数组
xiaowang[i] = thread;
}
//通过遍历start每个线程
for(int i = 0; i < xiaowang.length; i++){
xiaowang[i].start();
}
}
- 创建 Runnable 自定义线程类
/**
*创建测试线程,线程的工作是使 ThreadLocal 变量的的值发生变化,并重新写回
*测试线程之间是否会发生相互影响
*/
public static class TestThread implements Runnabble{
int id = 0;
public TestThread(int id){
this.id = id;
}
public void run(){
//搞清楚currentThread()的用处:
//currentThread()源码解释:返回对当前正在执行的线程对象的引用
//返回值:当前正在执行的线程
//Thread.currentThread().getName():返回当前执行的线程的名字
System.out.println(Thhread.currentThread().getName() + ":start");
//get()方法:拿到threadLocal对象 XYDD的i值,i则为当前变量副本
Integer q = XYDD.get();
//更改 q 的值,要怎么把新的 q 设置为副本,
//看下面的set()
q = q + id;
//set()的作用
//@@@*****下面解释*****@@@@@
XYYDD.set(q);
System.out.println(Thread.currentThread().getName()+":"+XYDD.get());
}
}
set() 的用处:
来看看源码的解释:将此线程局部变量的当前线程副本设置为指定值
放到这段代码块的意思就是:前面的get()方法已经拿到thresadLocal XYDD的初始化值,但是经过run()方法里的id,q的值变了,所以通过set()方法可以将新的 q 设置回去做新的副本
注意在这里怎么set()都是在该线程内部的entry[]搞定,不涉及其他线程和threadLocal
不管使set()还是get(),都是对自己独有的ThreadLocalMap进行操作的
也因为每个线程自己独有的ThreadLocalMap是别的线程访绝对问不到的
这就是为什么threadLocal能保证各线程之间的安全性
最后创建主线程
public static void main(String[] args){
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
思路都在注释里了,跟着走没问题的,下面注重对源码的分析
直接来理解一下变量的副本到底是怎么创建的,最好的方法就是解析所执行的方法:
源码如下:
public T get() {
------1. 先拿到调用这个get()方法的当前线程
Thread t = Thread.currentThread();
------2. 再把当前线程 t 作为参数传给了 getmap()方法
------3. 通过 getmap() 方法创建一个 map 对象!! 看下面的getmap() 解释
------4. 通过 getmap() 拿到每个线程自己独有的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
------判空
if (map != null) {
------5. 拿enty条目,通过取threadLocal键对应的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
------6.返回值
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
要搞明白就要先搞明白getMap()方法到底是什么:
ThreadLocalMap getMap(Thread t) {
-----调用 getMap()返回当前线程里的threadLocals
return t.threadLocals;
}
可以看到调用 getMap()返回线程里的 threadLocals ,再继续看这个 threadLocals 是什么:
/* ThreadLocal values pertaining to this thread. This map is maintainedby the ThreadLocal class.
与此线程相关的本地值。此映射由ThreadLocal类维护
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals 是一个定义在 ThreadLocal 里面的 ThreadLocalMap
所以每一个线程都有一个自己的成员变量,而这个成员类型是ThreadLocalMap,搞不懂ThreadLocalMap 是什么,没关系!继续:
static class ThreadLocalMap {
/**
The entries in this hash map extend WeakReference, using
its main ref field as the key (which is always a
ThreadLocal object). Note that null keys (i.e. entry.get()
== null) mean that the key is no longer referenced, so the
entry can be expunged from table. Such entries are referred to
as "stale entries" in t he code that follows.
*/
------定义一个Enty------
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
//第一个参数是 key, 第二个参数是 value;
//以threadLocal为键,以Object为Value
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
//Enty[] 被定义成了一个数组,因为传入的ThreadLocal可以是多个,一个线程拥有的ThreadLocal变量副本可能是多个
private Entry[] table;
private int size = 0;
...
Enty 为什么是个数组:
因为传入的ThreadLocal可以是多个,一个线程拥有的ThreadLocal变量副本可能是多个,数组初始化大小是16
而 ThreadLocalMap 表示每个线程自己会有一个 ThreadLocalMap,本质上还是一个Map, 只不过每个这个 Map 中间对每个条目来说是以 ThreadLocal 为键盘,以传入的 Object 的值为Value。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
其实 set() 也是差不多的, currentThread() 拿到当前线程,getMap() 拿 threadLocals,threadLocals 又是个 threadLocalMap,也就是拿 Enty 条目
有点区别的就是 createMap(),看下面吧
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
return threadLocals,得到的threadLocals 与set() 相同
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
拿到当前线程的 threadLocals,是通过 new 一个 ThreadLocalMap 类的实例实现,传入第一个参数是 this 代表当前线程,第二个参数是 firstValue
这个方法啊,也就是ThreadLocalMap 类中的方法,该咋实现咋实现
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
-------还是 key Value 类型
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
-------Enty[] 实例,大小16
table = new Entry[INITIAL_CAPACITY];
-------下面是判断ThreadLocal的副本有没重复,没有重复就返回
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
ThreadLocalMap 是 ThreadLocal 的静态内部类,每一个 Thread,都有一个自己的 ThreadLocalMap。
而ThreadLocalMap 里是一个 Entry 数组。
目的是 set() 中采用 nexHash,重复计算哈希值来判断ThreadLocal是否重复
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry 是 ThreadLocalMap 的静态内部类,ThreadLocal 与副本对象,做成了 key-value 的形式。
它继承了 WeakReference,总之它记录了两个信息,一个是 ThreadLocal 类型,一个是 Object 类型的值。反正 getEntry() 则是获取某个 ThreadLocal 对应的值, set() 就是更新或赋值相应的 ThreadLocal 对应的值。