Java ThreadLocal (Java代码实战-006)

ThreadLocal解决什么问题

由于 ThreadLocal 支持范型,如 ThreadLocal< StringBuilder >,为表述方便,后文用 变量 代表 ThreadLocal 本身,而用 实例 代表具体类型(如 StringBuidler )的实例。 

不恰当的理解
写这篇文章的一个原因在于,网上很多博客关于 ThreadLocal 的适用场景以及解决的问题,描述的并不清楚,甚至是错的。下面是常见的对于 ThreadLocal的介绍

ThreadLocal为解决多线程程序的并发问题提供了一种新的思路
ThreadLocal的目的是为了解决多线程访问资源时的共享问题

还有很多文章在对比 ThreadLocal 与 synchronize 的异同。既然是作比较,那应该是认为这两者解决相同或类似的问题。

上面的描述,问题在于,ThreadLocal 并不解决多线程 共享 变量的问题。既然变量不共享,那就更谈不上同步的问题。

合理的理解
ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不同的 Thread 中有不同的副本(实际是不同的实例,后文会详细阐述)。这里有几点需要注意

因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来
既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题
既无共享,何来同步问题,又何来解决同步问题一说?
那 ThreadLocal 到底解决了什么问题,又适用于什么样的场景?

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the 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).

核心意思是

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。后文会通过实例详细阐述该观点。另外,该场景下,并非必须使用 ThreadLocal ,其它方式完全可以实现同样的效果,只是 ThreadLocal 使得实现更简洁。

ThreadLocal用法

 java代码:

package Threads;

import java.util.concurrent.CountDownLatch;

/**
 * Created by xfyou 2018/5/25 18:32.
 */
public class ThreadLocalDemo {

    // 闭锁需要等待的线程数量
    private static final int THREADS_COUNT = 3;

    public static void main(String[] args) throws InterruptedException {

        /*在实时系统中的使用场景
         *
         * 实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。
         * 例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用一次countDown()方法就可以让所有的等待线程同时恢复执行。
         * 开始执行前等待N个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
         * 死锁检测:一个非常方便的使用场景是,你可以使用N个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
         */
        CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);

        InnerClass innerClass = new InnerClass();
        for (int i = 1; i <= THREADS_COUNT; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 4; j++) {
                        innerClass.add(String.valueOf(j));
                        innerClass.print();
                    }
                    innerClass.set("hello world");

                    /*
                     * 通知CountDownLatch对象,他们已经完成了各自的任务
                     * 每当一个线程完成了自己的任务后,计数器的值就会减1
                     * 所以当N个线程都调用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。
                     */
                    countDownLatch.countDown();
                }
            }, "Thread-" + i).start();
        }

        /*
         * 主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
         * CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。
         * 每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁(Latch)上等待的线程就可以恢复执行任务。
         */
        countDownLatch.await();

        System.out.println("所有线程执行完毕");
        System.out.println("主线程继续执行。。。");
    }

    private static class InnerClass {
        void add(String newStr) {
            StringBuilder str = Counter.counter.get();
            Counter.counter.set(str.append(newStr));
        }

        void print() {
            System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.counter.hashCode(),
                    Counter.counter.get().hashCode(),
                    Counter.counter.get().toString());
        }

        void set(String words) {
            Counter.counter.set(new StringBuilder(words));
            System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s,  Instance hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.counter.hashCode(),
                    Counter.counter.get().hashCode(),
                    Counter.counter.get().toString());
        }
    }

    private static class Counter {
        /*
         * get时如果线程本地变量为null,则默认初始化一个这个变量类型的实例。
         * StringBuilder为非线程安全的类型,通过ThreadLocal本地化则可以实现线程安全
         */
        private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() {
            @Override
            protected StringBuilder initialValue() {
                return new StringBuilder();
            }
        };
    }
}

 一种可能的运行结果如下:

Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:0
Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:01
Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:012
Thread name:Thread-2 , ThreadLocal hashcode:946838393, Instance hashcode:537578880, Value:0123
Set, Thread name:Thread-2 , ThreadLocal hashcode:946838393,  Instance hashcode:997620193, Value:hello world

Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:0
Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:01
Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:012
Thread name:Thread-3 , ThreadLocal hashcode:946838393, Instance hashcode:767376320, Value:0123
Set, Thread name:Thread-3 , ThreadLocal hashcode:946838393,  Instance hashcode:595251207, Value:hello world

Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:0
Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:01
Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:012
Thread name:Thread-1 , ThreadLocal hashcode:946838393, Instance hashcode:540065051, Value:0123
Set, Thread name:Thread-1 , ThreadLocal hashcode:946838393,  Instance hashcode:787957613, Value:hello world

所有线程执行完毕
主线程继续执行。。。

 运行结果分析:

1、所有线程访问的都是同一个ThreadLocal变量,其hashcode为:946838393(各线程访问的ThreadLocal在堆内存中的地址均为同一个);

2、各线程通过ThreadLocal对象的get()方法拿到的StringBuilder对象实例是不同的(hashcode不一样,实例在堆内存中的地址不一样);

3、各个线程将字符串追加进各自的 StringBuidler 实例内;

4、使用 set(T t) 方法后,ThreadLocal 变量所指向的 StringBuilder 实例被替换。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值