问题:
1、ThreadLocal 是什么?有哪些使用场景? 2、ThreadLocal的设计理念与作用? 3、什么叫线程局部变量?
1. ThreadLocal是什么?
在Java的多线程并发执行过程中,为了保证多个线程对变量的安全访问,可以将变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立值,不会出现一个线程读取变量时被另一个线程修改的现象。
ThreadLocal类通常被翻译为“线程本地变量”类或者“线程局部变量”类。
2. ThreadLocal基本作用?
ThreadLocal位于JDK的java.lang核心包中。如果程序创建了一个ThreadLocal实例,那么在访问这个变量的值时,每个线程都会拥有一个独立的、自己的本地值。“线程本地变量”可以看成专属于线程的变量,不受其他线程干扰,保存着线程的专属数据。当线程结束后,每个线程所拥有的那个本地值会被释放。在多线程并发操作“线程本地变量”的时候,线程各自操作的是自己的本地值,从而规避了线程安全问题。
Java程序可以调用ThreadLocal的成员方法进行本地值的操作,具体的成员方法:
(1) set(T value) :设置当前线程在“线程本地变量”实例中绑定的本地值; (2) T get() :获取当前线程在“线程本地变量”实例中绑定的本地值; (3) remove() :移除当前线程在“线程本地变量”实例中绑定的本地值;
下面的例子通过ThreadLocal的成员方法进行“线程本地变量”中线程本地值的设置、获取和移除,具体的代码如下:
@Data
public class Person {
private int age = 10;
}
public class TestThreadLocal {
private static final ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=0;i<5;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
if(threadLocal.get()==null){
threadLocal.set(new Person());
}
System.out.println("初始的本地值:"+threadLocal.get());
for(int i=0;i<10;i++){
Person foo = threadLocal.get();
foo.setAge(foo.getAge()+1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("累加10次之后的本地值:"+threadLocal.get());
threadLocal.remove();
}
});
}
}
}
执行结果:
初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
初始的本地值:Person(age=10)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)
累加10次之后的本地值:Person(age=20)
在“线程本地变量”(threadLocal)l中,每一个线程都绑定了一个独立的值(Person对象),这些值对象是线程的私有财产,可以理解为线程的本地值,线程的每一次操作都是在自己的同一个本地值上进行的。
如果线程尚未在本地变量(如threadLocal)中绑定一个值,直接通过调用get()方法获取本地值会获取到一个空值,此时可以通过调用set()方法设置一个值作为初始值,具体的代码如下:
if(threadLocal.get()==null){
threadLocal.set(new Person());
}
在当前线程尚未绑定值时,如果希望从线程本地变量获取到初始值,而且不想采用以上的“判空后设值”这种相对烦琐的方式,可以调用ThreadLocal.withInitial(…) 静态工厂方法,在定义ThreadLocal对象时设置一个获取初始值的回调函数,具体的代码如下:
private static final ThreadLocal<Person> threadLocal = ThreadLocal.withInitial(()->new Person());
以上代码并没有使用new ThreadLocal<Person>() 构造一个ThreadLocal对象,而是调用withInitial(…)工厂方法创建一个ThreadLocal对象,并传递了一个获取初始值的Lambda回调函数。在线程尚未绑定值而直接从“线程本地变量”获取值时,将会取得回调函数被调用之后所返回的值。
3. ThreadLocal的应用场景?
ThreadLocal是解决线程安全问题的一个较好的方案,它通过为每个线程提供一个独立的本地值去解决并发访问的冲突问题。在很多情况下,使用ThreadLocal比直接使用同步机制(如synchronized)解决线程安全问题更简单、更方便,且结果程序拥有更高的并发性。
ThreadLocal的使用场景大致可以分为以下两类:
1. 线程隔离
ThreadLocal的主要价值在于线程隔离,ThreadLocal中的数据只属于当前线程,其本地值对别的线程是不可见的,在多线程环境下,可以防止自己的变量被其他线程篡改。另外,由于各个线程之间的数据相互隔离,避免了同步加锁带来的性能损失,大大提升了并发性的性能。
ThreadLocal在线程隔离的常用案例为:可以为每个线程绑定一个用户会话信息、数据库连接、HTTP请求等,这样一个线程所有调用到的处理函数都可以非常方便地访问这些资源。常见的ThreadLocal使用场景为数据库连接独享、Session数据管理等。
在“线程隔离”场景中,使用ThreadLocal的典型案例为:可以为每个线程绑定一个数据库连接,使得这个数据库连接为线程所独享,从而避免数据库连接被混用而导致操作异常问题。
下面的代码来自Hibernate,代码中通过ThreadLocal进行数据库连接(Session)的“线程本地化”存储,主要的代码如下:
private static final ThreadLocal threadSession= new ThreadLocal();
public static Session getSession(){
Session s = (Session)threadSession.get();
if(s==null){
getSessionFactory().openSession();
threadSession.set(s);
}
}
Hibernate对数据库连接进行了封装,一个Session代表一个数据库连接。通过以上代码可以看到,在Hibernate的getSession()方法中,首先判断当前线程中有没有放进去Session,如果还没有,那么通过sessionFactory().openSession()来创建一个Session,再将Session设置到ThreadLocal变量中,这个Session相当于线程的私有变量,而不是所有线程共用的,显然其他线程中是取不到这个Session的。
一般来说,完成数据库操作之后程序会将Session关闭,从而节省数据库连接资源。如果Session的使用方式为共享而不是独占,在这种情况下,Session是多线程共享使用的,如果某个线程使用完成之后直接将Session关闭,其他线程在操作Session时就会报错。所以Hibernate通过ThreadLocal非常简单地实现了数据库连接的安全使用。
2. 跨函数传递
数据通常用于同一个线程内,跨类、跨方法传递数据时,如果不用ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。
由于ThreadLocal的特性,同一线程在某些地方进行设置,在随后的任意地方都可以获取到。线程执行过程中所执行到的函数都能读写ThreadLocal变量的线程本地值,从而可以方便地实现跨函数的数据传递。使用ThreadLocal保存函数之间需要传递的数据,在需要的地方直接获取,也能避免通过参数传递数据带来的高耦合。
在“跨函数传递数据”场景中使用ThreadLocal的典型案例为:可以为每个线程绑定一个Session(用户会话)信息,这样一个线程所有调用到的代码都可以非常方便地访问这个本地会话,而不需要通过参数传递。
ThreadLocal在“跨函数数据传递”场景的典型应用有很多:
(1)用来传递请求过程中的用户ID。
(2)用来传递请求过程中的用户会话(Session)。
(3)用来传递HTTP的用户请求实例HttpRequest。
(4)其他需要在函数之间频繁传递的数据。
public class Test {
private static final ThreadLocal<String> sidLocal= new ThreadLocal("sidLocal");
private static final ThreadLocal<UserDTO> sessionUserLocal = new ThreadLocal<>("sessionUserLocal");
private static final ThreadLocal<HttpSession> sessionLocal = new ThreadLocal<>("sessionLocal");
public static Session setSession(HttpSession httpSession){
sessionLocal.set(httpSession);
}
public static HttpSession getSession(){
HttpSession session = sessionLocal.get();
Assert.notNull(session,"session 未设置");
return session;
}
}
|