java 的 ThreadLocal

除了前面一直讲的加锁的同步方式之外,还有一种保证一种规避多线程访问出现线程不安全的方法就是使用 ThreadLocal。

在多线程环境下,每个线程都有自己的数据,一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有自己的能看见,不会影响其他线程。

ThreadLocal 能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到线程按安全的目的。说白了,ThreadLocal就是为了在多线程环境下保证成员变量的安全。

最常用的地方,就是为每个线程都绑定一个数据库连接,http请求、用户身份信息等,这样一个线程多所有调用到的方法就能很方便的访问这些资源;常用的方法有三个,get、set和initialValue。

jdk 建议将 threadLocal 定义为 private static。

一、ThreadLocal 示例

使用方法简单示例:

/**
* ThreadLocal:每个线程自己的数据,不会影响其他线程。
*/
public class MYThreadLocal {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
    public static void main(String[] args) {
        for (int i=0; i<5; i++){
            new Thread(new Test()).start();
        }
    }

    public static class Test implements Runnable{
        @Override
        public void run() {
            Integer i = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"得到"+i);
            threadLocal.set(i-1);
            System.out.println(Thread.currentThread().getName()+"剩下"+threadLocal.get());
        }
    }
}

前面说了这个东西可以避免线程不安全,其实写完代码更能理解,就是直接规避了共同资源这一件事,本来就是在各用各的,没有公共资源的问题,所以也不牵扯线程安全了。也就是说的一个线程级别的变量

二、源码

ThreadLocal 的源码,如何实现这又是另外一个主题了:

(待更新链接)

三、ThreadLocal使用要注意的上下文问题

3.1 ThreadLocal

来看这个代码:

public class MYThreadLocal1 {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
    public static void main(String[] args) {
        new Thread(new Test()).start();
    }

    public static class Test implements Runnable{
        public Test(){
            threadLocal.set(-100);
            System.out.println(Thread.currentThread().getName() + "是:"+threadLocal.get());
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"是:"+threadLocal.get());
        }
    }
}

他的输出是什么呢?

在这里插入图片描述
可以看到,输出的线程自己的独有的变量这一点,-100虽然是在 Test 里设置,可是修改了main线程:

  • 第一行,这个线程 Test 的构造器虽然 set 了值,但是,输出的结果对应的线程却是他所在的线程,也就是调用他的那个线程。
  • 而第二行,run 方法是由new 出的 Test 线程调用的,所以输出的结果对应的是他所在的线程。

也就是说,线程的构造器属于对应的线程体;而 run 方法则是属于本线程。

3.2 InheritableThreadLocal

顾名思义,继承线程。

使用这个类,可以把变量从上下文环境进行继承。

我们如果按照普通 ThreadLocal 写出:

public class MYThreadLocal1 {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set(2);
        System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());
        
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());
        }).start();
    }
}

如果是这样的代码,会输出 2 和 null,因为线程各自拥有自己的变量,而第二个线程虽然在main线程内部,但是仍然是独立的。

如果使用InheritableThreadLocal,则会把上下文关系进行继承,修改类为:

private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();

则上面的程序会输出 2 和 2,相当于默认情况下,main线程会将自己的数据给子线程 拷贝 一份。

如果不想继承,当然子线程也可以自己改自己的。

private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
    threadLocal.set(2);
    System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());

    new Thread(()->{
        threadLocal.set(1);
        System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());
    }).start();
}

这样就会输出 2 和 1.1

你可能感兴趣的:(Java学习)