ThreadLocal 是什么?
ThreadLocal并不是一个Thread,而它是Thread的局部变量,可以在当前线程内存储数据,并且这个数据只能在当前线程中可以得到get和set,跟普通变量的不同之处在于每一个线程读取的ThreadLocal变量是对应的互相独立的。主要有这几个方法:initialValue(初始化方法)、get、set、remove方法。
做一个简单的对比测试说明一下
public class ThreadLocalTest {
// 普通变量
public Long mGeneral = 0L;
// ThreadLocal变量
public ThreadLocal<Long> mThreadLocal = new ThreadLocal() {
@Override
protected Object initialValue() {
log.info("当前线程ID={}初始化initialValue", Thread.currentThread().getId());
return 0L;
}
};
}
测试:
public static void main(String[] args) {
log.info("main方法线程ID={}", Thread.currentThread().getId());
ThreadLocalTest threadLocalTest = new ThreadLocalTest();
threadLocalTest.mGeneral = 10L;
threadLocalTest.mThreadLocal.set(100L);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
log.info("thread1当前线程ID={}", Thread.currentThread().getId());
log.info("thread1线程中general={}", threadLocalTest.mGeneral);
log.info("thread1线程中mThreadLocal.get()={}", threadLocalTest.mThreadLocal.get());
}
});
thread1.start();
log.info("main方法线程中general{}", threadLocalTest.mGeneral);
log.info("main方法线程中mThreadLocal.get(){}", threadLocalTest.mThreadLocal.get());
}
提示一下:main会开启一个主线程,主线程中又开启了一个子线程thread1。它们都可以访问threadLocalTest对象。
输出的结果如下:
[ main]main方法线程ID=1
[ main]main方法线程中general10
[Thread-1]thread1当前线程ID=13
[Thread-1]thread1线程中general=10
[ main]main方法线程中mThreadLocal.get()100
[Thread-1]当前线程ID=13初始化initialValue
[Thread-1]thread1线程中mThreadLocal.get()=0
说明:
- general在main线程修改之后,在thread1访问的是修改之后的值,general被共个两个线程共享。mThreadLocal在main线程修改之后,在thread1访问时扔会被初始化,mThreadLocal没有被共享。
- ThreadLocal在get之前如果没有被set或被remove,那么会先执行initialValue方法,再返回initialValue值。
ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
举个栗子,假如让100个并发累计计数10000次,就是100个线程,每个线程计数100次,如下所示:
Val计数对象,每次计数消耗0.1秒
@Getter
@Setter
public class Val {
private Long value;
public void add() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (null == value) {
value = 0L;
}
value = value + 1L;
}
}
public class ThreadLocalTest2 {
public static Val val = new Val();
public static void main(String[] args) throws InterruptedException {
// 模拟100个并发
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//每个并发计数100次
for (int i = 0; i < 100; i++) {
val.add();
}
log.info("线程{}的100次执行完毕", Thread.currentThread().getId());
}
}).start();
}
// 等60秒,让上面10个线程执行完毕
Thread.sleep(60 * 1000);
log.info("val值={}", val.getValue());
}
}
执行结果每次都不一样,并且小于10000。如[ main]val值=8684。是因为val.value被各个线程共享,在value=value+1的时候,会被别的线程value重置掉。用ThreadLocal 可以解决这个问题。
public class ThreadLocalTest2 {
public static Set<Val> threadValSet = new HashSet<>();
public synchronized static void addThreadValSet(Val val) {
threadValSet.add(val);
}
public static ThreadLocal<Val> mThreadLocal = new ThreadLocal() {
@Override
protected Val initialValue() {
Val val = new Val();
threadValSet.add(val);
return val;
}
};
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//每个并发计数100次
Val val = mThreadLocal.get();
for (int i = 0; i < 100; i++) {
val.add();
}
log.info("线程{}的100次执行完毕,value={}", Thread.currentThread().getId(), val.getValue());
}
}).start();
}
// 等60秒,让上面10个线程执行完毕
Thread.sleep(60 * 1000);
Long count = 0L;
for (Val val : threadValSet) {
count = count + val.getValue();
}
log.info("val累计值={}", count);
}
}
说明一下,上面那种并发也可以在Val的add()方法上加synchronized,但是执行效率在低很多。这里加synchronized只是向Set添加一个值。
结果:[ main]val累计值=10000。每个线程的mThreadLocal里的val都是100。
网友评论