❤️并发编程❤️ThreadLocal基础

文章目录

(一)基础认识

(1)概述

定义:提供线程局部变量,一个线程局部变量在多个线程中,分别有独立的值(副本)
特点:简单(开箱即用)、快速(无额外开销)、安全(线程安全)
场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等)
实现原理:Java中用哈希表实现
应用范围:几乎所有提供多线程特征的语言

❤️并发编程❤️ThreadLocal基础
❤️并发编程❤️ThreadLocal基础
❤️并发编程❤️ThreadLocal基础

(2)ThreadLocal API

public class ThreadLocal<T>extends Object

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

 T get() 
          返回此线程局部变量的当前线程副本中的值。 
protected  T initialValue() 
          返回此线程局部变量的当前线程的“初始值”。 
 void remove() 
          移除此线程局部变量当前线程的值。 
 void set(T value) 
          将此线程局部变量的当前线程副本中的值设置为指定值。 

(3)基本实例

public class Demo1 {

    private static ThreadLocal<Long> threadLocal = new ThreadLocal<Long>() {
        @Override
        protected Long initialValue() {
            System.out.println("initialValue...");
            return Thread.currentThread().getId();
        }
    };

    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                System.out.println("线程二:" + Thread.currentThread().getId());
                System.out.println("线程二:" + threadLocal.get());
                threadLocal.set(100L);
                System.out.println("线程二:" + threadLocal.get());

            }
        }.start();
        System.out.println("主线程:" + threadLocal.get());
    }
}

❤️并发编程❤️ThreadLocal基础
案例代码结论:
1:每个线程都拥有独立的ThreadLocal对象
2:initialValue()方法是延迟加载的,当调用get方法时才调用,如果在get前有set方法的话不会执行
3:某个线程对于ThreadLocal的修改不会影响到其他线程的ThreadLocal数据

(二)ThreadLocal的使用——线程运行耗时统计

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个 线程上的一个值。 可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
在下面的例子中,构建了一个常用的Profiler类,它具有begin()和end()两个 方法,而end()方法返回从begin()方法调用开始到end()方法被调用时的时间差,单位是毫秒。


import java.util.concurrent.TimeUnit;
public class Profiler {

    /**
     * 第一次get()方法调用时会进行初始化(如果set方法没有调用),每个线程会调用一次
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>() {
        @Override
        protected Long initialValue() {
            return System.currentTimeMillis();
        } };

    public static final void begin() {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    public static final long end() {
        return System.currentTimeMillis() - TIME_THREADLOCAL.get();
    }
    public static void main(String[] args) throws Exception {
        Profiler.begin(); TimeUnit.SECONDS.sleep(1);
        System.out.println("Cost: " + Profiler.end() + " mills");
    }
}

Profiler可以被复用在方法调用耗时统计的功能上,在方法的入口前执行begin()方法,在方法调用后执行end()方法,好处是两个方法的调用不用在一个方法或者类中,比如在AOP(面 向方面编程)中,可以在方法调用前的切入点执行begin()方法,而在方法调用后的切入点执行 end()方法,这样依旧可以获得方法的执行耗时。

(三)使用场景分析

在通常的业务开发中,ThreadLocal 有两种典型的使用场景。

场景1,ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,每个线程都只能修改自己所拥有的副本, 而不会影响其他线程的副本,这样就让原本在并发情况下,线程不安全的情况变成了线程安全的情况。

场景2,ThreadLocal 用作每个线程内需要独立保存信息的场景,供其他方法更方便得获取该信息,每个线程获取到的信息都可能是不一样的,前面执行的方法设置了信息后,后续方法可以通过 ThreadLocal 直接获取到,避免了传参。

上一篇:JDK成长记12:ThreadLocal (下)


下一篇:Java 并发编程:ThreadLocal 简单介绍