一、什么是线程封闭?
多线程访问共享可变数据时,线程间执行顺序的不同可能导致程序运行结果的不同,即线程安全问题;所以在多线程环境下,不涉及到共享的数据,就要通过线程封闭防止数据被共享修改,将数据封闭在各自的线程之中,这种避免同步的技术就是线程封闭
。
二、线程封闭的实现
1、ThreadLocal
ThreadLocal是Java中一种特别的线程级别的变量,当线程访问时,会为每个线程创建一个变量的副本,副本之间彼此独立,互不影响,从代码上看不同线程访问的是同一个变量,但实际上不同线程访问ThreadLocal时,获取到的封装其中的数据存储在不同的内存中,这保证了线程安全,代码示例如下:
// 线程封闭示例
public class Demo7 {
/** threadLocal变量,每个线程都有一个副本,互不干扰 */
public static ThreadLocal<String> value = new ThreadLocal<String>();
public void threadLocalTest() throws Exception {
value.set("这是主线程设置的123"); // 主线程设置值
String v = value.get();
System.out.println("线程1执行之前,主线程取到的值:" + v);
new Thread(new Runnable() {
@Override
public void run() {
String v = value.get();
System.out.println("线程1取到的值:" + v);
// 设置threadLocal
value.set("这是线程1设置的456");
v = value.get();
System.out.println("重新设置之后,线程1取到的值:" + v);
System.out.println("线程1执行结束");
}
}).start();
Thread.sleep(5000L); // 等待所有线程执行结束
v = value.get();
System.out.println("线程1执行之后,主线程取到的值:" + v);
}
public static void main(String[] args) throws Exception {
new Demo7().threadLocalTest();
}
}
【代码解析】代码中有两个线程:主线程和匿名线程(姑且将其称为线程1),主线程率先访问ThreadLocal变量并设置其包装的String对象的值为 这是主线程设置的123
打印输出设置后的效果;接着执行线程1,线程1也去访问ThreadLocal,但是它拿到的数据值是空的,因为此时JVM为线程1中的ThreadLocal包装的数据分配了一个新的内存,接着线程1设置值并打印出来;线程1执行结束后,回到主线程,打印出来的值依然是执行线程1前主线程设置的值,说明两个线程对ThreadLocal的读取和设置操作是隔离的,互不影响。
执行结果:
如何理解ThreadLocal是一种线程级别的变量?
每个线程都会有自己的栈,正常情况下多个线程访问一个共享对象时,是在自己的栈中持有对这个对象的引用,指向堆内存中的内存对象,如图所示:
而当线程访问的是ThreadLocal时,ThreadLocal会为每个线程单独创建一个对象,不同线程对持有的对象进行操作互不干扰,也无法通过线程发布,是一种绝对安全的线程隔离,保证线程安全,如图所示:
从某种程度上看,ThreadLocal可以看成是线程跟数据对象映射的Map,即Map<Thread, T>,每个线程读取数据时就通过当前线程去map里面get,从这个角度看,
threadLocal.get() = map.get(Thread.currentThread())
threadLocal.set(obj) = map.put(Thread.currentThread(), obj)
2、局部变量
局部变量被封闭在线程的栈中,而线程栈是属于线程私有的内存区域,因此其他线程无法访问,从而保证线程安全,常见的局部变量有方法中的局部变量,循环中的局部变量等。
public void fun() {
String str = ...;
}
while (true) {
String str = ...;
}
for (int i = 0; i < length: i++) {
int j = ...;
}
网友评论