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