/**
* 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).
*
* For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is assigned the first time it invokes {@code ThreadId.get()}
* and remains unchanged on subsequent calls.
*
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* private static final ThreadLocal<Integer> threadId =
* new ThreadLocal<Integer>() {
* @Override protected Integer initialValue() {
* return nextId.getAndIncrement();
* }
* };
*
* // Returns the current thread's unique ID, assigning it if necessary
* public static int get() {
* return threadId.get();
* }
* }
*
* Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the {@code ThreadLocal}
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal {
...
从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
总结:
2.1 常用方法
在使用之前,我们先来认识几个ThreadLocal的常用方法
方法声明 | 描述 |
ThreadLocal() | 创建ThreadLocal对象 |
public void set( T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
2.2 使用案例
我们来看下面这个案例 , 感受一下ThreadLocal 线程隔离的特点:
/*
* 需求: 线程隔离
* 在多线程并发的场景下, 每个线程中的变量都是相互独立
* 线程A : 设置(变量1 ) 获取(变量1)
* 线程B : 设置(变量2 ) 获取(变量2)
*
* */
public class MyDemo01 {
//变量
private String content;
private String getContent() {
return content;
}
private void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyDemo01 demo = new MyDemo01();
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
/*
每个线程: 存一个变量 , 过一会 取出这个变量
*/
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println("-----------------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
});
thread.setName("线程" + i); //线程0~4
thread.start();
}
}
}
打印结果如下:
-----------------------
-----------------------
线程3--->线程3的数据
-----------------------
-----------------------
线程1--->线程3的数据
线程2--->线程3的数据
线程0--->线程3的数据
-----------------------
线程4--->线程4的数据
-----------------------
线程6--->线程6的数据
-----------------------
-----------------------
线程7--->线程7的数据
线程5--->线程7的数据
-----------------------
线程8--->线程8的数据
-----------------------
线程9--->线程9的数据
-----------------------
线程17--->线程17的数据
-----------------------
线程12--->线程12的数据
-----------------------
线程13--->线程13的数据
-----------------------
线程18--->线程18的数据
-----------------------
线程19--->线程19的数据
-----------------------
线程15--->线程19的数据
-----------------------
线程11--->线程11的数据
-----------------------
线程10--->线程10的数据
-----------------------
线程16--->线程16的数据
-----------------------
线程14--->线程14的数据
Process finished with exit code 0
从结果可以看出多个线程在访问同一个变量的时候出现的异常,线程间的数据没有隔离。下面我们来看下采用 ThreadLocal 的方式来解决这个问题的例子
/*
* 需求: 线程隔离
* 在多线程并发的场景下, 每个线程中的变量都是相互独立
* 线程A : 设置(变量1 ) 获取(变量1)
* 线程B : 设置(变量2 ) 获取(变量2)
*
* ThreadLocal :
* 1. set() : 将变量绑定到当前线程中
* 2. get() : 获取当前线程绑定的变量
* */
public class MyDemo01 {
ThreadLocal tl = new ThreadLocal<>();
//变量
private String content;
private String getContent() {
return tl.get();
}
private void setContent(String content) {
//变量content绑定到当前线程
tl.set(content);
}
public static void main(String[] args) {
MyDemo01 demo = new MyDemo01();
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
/*
每个线程: 存一个变量 , 过一会 取出这个变量
*/
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println("-----------------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
});
thread.setName("线程" + i); //线程0~4
thread.start();
}
}
}
打印结果如下:
-----------------------
-----------------------
线程2--->线程2的数据
线程0--->线程0的数据
-----------------------
线程1--->线程1的数据
-----------------------
-----------------------
线程3--->线程3的数据
线程4--->线程4的数据
-----------------------
-----------------------
-----------------------
线程14--->线程14的数据
线程5--->线程5的数据
-----------------------
线程8--->线程8的数据
-----------------------
线程10--->线程10的数据
-----------------------
线程11--->线程11的数据
-----------------------
-----------------------
-----------------------
线程9--->线程9的数据
-----------------------
线程16--->线程16的数据
线程6--->线程6的数据
-----------------------
线程17--->线程17的数据
线程7--->线程7的数据
-----------------------
-----------------------
线程13--->线程13的数据
-----------------------
线程12--->线程12的数据
线程15--->线程15的数据
线程19--->线程19的数据
-----------------------
线程18--->线程18的数据
Process finished with exit code 0
从结果来看,这样很好的解决了多线程之间数据隔离的问题,十分方便。
3.1 synchronized同步方式
这里可能有的朋友会觉得在上述例子中我们完全可以通过加锁来实现这个功能。我们首先来看一下用synchronized代码块实现的效果:
/*
* 需求: 线程隔离
* 在多线程并发的场景下, 每个线程中的变量都是相互独立
* 线程A : 设置(变量1 ) 获取(变量1)
* 线程B : 设置(变量2 ) 获取(变量2)
*
* */
public class MyDemo02 {
//变量
private String content;
private String getContent() {
return content;
}
private void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyDemo02 demo = new MyDemo02();
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
/*
每个线程: 存一个变量 , 过一会 取出这个变量
*/
synchronized (MyDemo02.class){
demo.setContent(Thread.currentThread().getName() + "的数据");
System.out.println("-----------------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
}
});
thread.setName("线程" + i); //线程0~4
thread.start();
}
}
}
打印结果:
-----------------------
线程0--->线程0的数据
-----------------------
线程4--->线程4的数据
-----------------------
线程7--->线程7的数据
-----------------------
线程3--->线程3的数据
-----------------------
线程2--->线程2的数据
-----------------------
线程11--->线程11的数据
-----------------------
线程1--->线程1的数据
-----------------------
线程8--->线程8的数据
-----------------------
线程9--->线程9的数据
-----------------------
线程10--->线程10的数据
-----------------------
线程6--->线程6的数据
-----------------------
线程5--->线程5的数据
-----------------------
线程18--->线程18的数据
-----------------------
线程17--->线程17的数据
-----------------------
线程13--->线程13的数据
-----------------------
线程14--->线程14的数据
-----------------------
线程16--->线程16的数据
-----------------------
线程15--->线程15的数据
-----------------------
线程12--->线程12的数据
-----------------------
线程19--->线程19的数据
Process finished with exit code 0
从结果可以发现, 加锁确实可以解决这个问题,但是在这里我们强调的是线程数据隔离的问题,并不是多线程共享数据的问题, 在这个案例中使用synchronized关键字是不合适的。
3.2 ThreadLocal与synchronized的区别
虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同。
synchronized | ThreadLocal | |
原理 | 同步机制采用'以时间换空间'的方式, 只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用'以空间换时间'的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
总结:在刚刚的案例中,虽然使用ThreadLocal和synchronized都能解决问题,但是使用ThreadLocal更为合适,因为这样可以使程序拥有更高的并发性。