ThreadLocal 的第一个使用场景
用于线程隔离, 给每个线程都分配一个独立的对象, 避免多个线程之间对资源进行抢占
ThreadLocal 第一个场景: 用于线程隔离, 给每个线程都分配一个独立的对象(一般是重复应用的工具类), 避免多个线程之间对资源进行抢占.
用一句话说就是: 把一个变量复制多份, 给每一个线程都分一份
生活中的例子
课堂上需要在课本上做笔记, 如果只有一本课本, 全本同学都要用, 那么这个课本就会被画的乱七八糟(线程安全问题). 如果把这个课本进行复印, 每个同学一本, 那么课本就不会非常整洁.
程序的例子
有1000个任务, 被放进了一个含有10个线程的线程池处理. 每个任务都很简单, 传入一个不同的数字给 SimpleDateFormat.format(), 返回一个字符串, 表示自1970.01.01 00:00:00 GMT 以来的时间.
我们可以在每一个任务里面 new 一个 SimpleDateFormat 对象. 但是这样开销会很大, 况,且每次new 出来的对象都是完全相同的对象, 只是传入的参数不同. 如果给每个线程都分配一个 SimpleDateFormat 对象, 每个线程执行任务时都用这个对象, 只是传入的参数不同, 那么就完美解决了这个问题.
用代码说明
我们定义了一个 normalUsage05类, 里面有一个方法 data(), 传入一个整型数字 单位 毫秒, 返回自 1970.01.01 00:00:00 GMT以来的计时. 这个方法会被 main() 方法内的 for 循环 提交到一个线程池内执行. for 循环内每一次循环 向 date() 方法传入的参数都不同, 所以生成的时间也应该是不同的
public class normalUsage05 {
//参数单位 毫秒, 返回自1970.01.01 00:00:00 GMT 计时
public String date(int seconds){
Date date = new Date(1000*seconds);
//第一次 对象.get() 就调用 initialValue() 将ThreadLocal 初始化
SimpleDateFormat sDF=ThreadSafeDateFormatter.dFThreadLocal.get();
String s =sDF.format(date);
synchronized (normalUsage05.class){
dateSet.add(s);
}
return s;
}
public static ExecutorService threadPool= Executors.newFixedThreadPool(10);
public static HashSet<String> dateSet= new HashSet<>();
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
final int finalI =i ;
threadPool.submit(new Runnable() {
@Override
public void run() {
String date= new normalUsage05().date(finalI);
System.out.println(date);
}
});
}
ThreadSafeDateFormatter.dFThreadLocal.remove();
threadPool.shutdown();
while (!threadPool.isTerminated()){
}
System.out.println(dateSet.size());
}
}
值得注意的是在 data() 方法内通过ThreadSafeDateFormatter.dFThreadLocal.get() 获得 SimDateFomat 对象. ThreadSafeDateFormatter 是我们定义的一个类, 里面包含了一个静态 ThreadLocal 对象 dFThreadLocal, dFThreadLocal 内会创建一个 SimpleDateFormat 对象, 给整个线程使用.
在 main() 函数的末尾, 我们调用 ThreadLocal对象.remove() 方法对内存进行释放, 最后关闭线程池.
我们在额外用了一个 HashSet对象 用来记录时间, 每一个任务内都需要记录一下时间放入 HashSet对象 , 为了多线程访问HashSet 对象安全, 我们使用synchronized 关键字进行上锁.若 HashSet对象 内元素的个数为1000, 说明没有产生线程安全问题.
public class ThreadSafeDateFormatter {
public static ThreadLocal<SimpleDateFormat> dFThreadLocal =new ThreadLocal<SimpleDateFormat>(){
//ThreadLocal对象生成时机不同, 可以选择 重写 initialValue() 方法或者 对象.set() 来初始
//使用initialValue() 会在第一次 对象.get() 的时候把对象初始化出来
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
};
}
ThreadLocal 对象内需要重写 initiaValue() 为这个对象进行初始化, 当调用 ThreadLocal 对象.get() 方法时就会调用 intialValue() 进行初始化.
测试一下
部分结果
1970-01-01 08:15:51
1970-01-01 08:15:45
1970-01-01 08:15:43
1970-01-01 08:15:40
1970-01-01 08:16:35
1970-01-01 08:16:27
1970-01-01 08:16:31
1970-01-01 08:16:28
1000
我们重点关注 HashSet 内的元素个数. 输出显示1000, 说明1000个任务产生的时间都是不同的, 符合预期, 没有产生线程安全问题
Last updated