ThreadLocal的概述
概念:
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
引入:
class ConnectionManager {
private static Connection connect = null;
public static Connection openConnection() {
if(connect == null){
connect = DriverManager.getConnection();
}
return connect;
}
public static void closeConnection() {
if(connect!=null)
connect.close();
}
}
假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?
很显然,在多线程中使用会存在线程安全问题:
第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;
第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。
所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。
这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。
那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?
事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。
设计初衷:
提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。
源码解释:
- 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的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
其函数签名如下:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
看起来什么也没有做,所以应该可以看成是一个标记。
再来看下对initial value(初始值)的定义,返回值为null,说明这样的方法一般都需要方法重载。
initial value()函数
protected T initialValue() {
return null;
}
重载示例:
ThreadLocal<String> myThreadlocal = new ThreadLocal<String>(){
@Override
protected String initialValue(){
return String.valueOf("WD");
}
};
get()方法:
该函数用来获取与当前线程关联的ThreadLocal的值,函数签名如下:
public T get(){...}
如果当前线程没有该ThreadLocal的值,则调用initialValue函数获取初始值返回。
set()方法:
set函数用来设置当前线程的该ThreadLocal的值,函数签名如下:
public void set(T value){...}
remove()方法:
set函数用来设置当前线程的该ThreadLocal的值,函数签名如下:
public void remove(){...}
有时需要手动调用这个方法,防止内存的泄露。
ThreadLocal简单应用
通过代码来理解一下ThreaddLocal的作用:
import org.junit.Test;
public class ThreadLocalTest {
@Test
public void test() {
ThreadLocal<String> myThreadlocal = new ThreadLocal<String>();
myThreadlocal.set("WD");
String s = myThreadlocal.get();
System.out.println("s=" + s);
myThreadlocal.set("cskaoyan");
String s1 = myThreadlocal.get();
System.out.println("s1="+s1);
MyThread myThread = new MyThread(myThreadlocal);
myThread.start();
//当前线程睡觉
Thread thread = Thread.currentThread();
try {
thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String s3 = myThreadlocal.get();
System.out.println("s3="+s3);
}
}
class MyThread extends Thread{
ThreadLocal<String> threadLocal;
public MyThread(ThreadLocal<String> threadLocal) {
this.threadLocal = threadLocal;
}
@Override
public void run() {
String s = threadLocal.get();
System.out.println("s in 子线程 =" +s);
threadLocal.set("aaaa");
System.out.println("s in 子线程 ="+threadLocal.get());
}
}
猜一猜结果?
s=WD
s1=cskaoyan
s in 子线程 =null
s in 子线程 =aaaa
s3=cskaoyan
是不是觉得后面三条解决很奇怪,为什么会获取不到子线程中运行的对象呢??
这就涉及到 ThreadLocal的处理线程并发的机制:
ThreadLocal原理:
因为ThreadLocal在每个线程中对该变量会创建一个 副本(counterparts),即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间不会相互影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
内部实现原理:
如果让你来设计,你会怎么做呢?才能够让每个线程都是安全的。