ThreadLocal的基本用法

1.基本用法

Java ThreadLocal类容许您建立只能由同一线程读写的变量。所以,即便两个线程正在执行相同的代码,而且代码引用了相同的ThreadLocal变量,这两个线程也不能看到彼此的ThreadLocal变量。所以,Java ThreadLocal类提供了一种使代码线程安全的简单方法。java

//建立
private ThreadLocal threadLocal = new ThreadLocal();
//一旦建立了ThreadLocal,就能够使用它的set()方法设置要存储在其中的值。
threadLocal.set("A thread local value");
//获取值
String threadLocalValue = (String) threadLocal.get();
//移除一个值
threadLocal.remove();

2. 普通ThreadLocal

//您能够建立具备泛型类型的ThreadLocal。使用泛型类型时,只能将泛型类型的对象设置为ThreadLocal的值。
//此外,您没必要对get()返回的值进行类型转换。
private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
//如今只能在ThreadLocal实例中存储字符串。另外,您不须要对从ThreadLocal得到的值进行类型转换
myThreadLocal.set("Hello ThreadLocal");
String threadLocalValue = myThreadLocal.get();

3.初始化ThreadLocal的值

能够为Java ThreadLocal设置一个初始值,该值将在第一次调用get()时使用.
有两种方式指定ThreadLocal初始值:安全

3.1建立一个ThreadLocal子类,该子类覆盖initialValue()方法:

//为Java ThreadLocal变量指定初始值的第一种方法是建立ThreadLocal的子类,该子类覆盖了它的initialValue()方法。
//建立ThreadLocal子类的最简单方法是直接在建立ThreadLocal变量的地方建立一个匿名子类。下面是建匿名子类的示例,
//该子类覆盖了initialValue()方法
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override protected String initialValue() {
        return String.valueOf(System.currentTimeMillis());
    }
};
//注意,不一样的线程仍然会看到不一样的初始值。每一个线程将建立本身的初始值。
//只有当从initialValue()方法返回彻底相同的对象时,全部线程才会看到相同的对象。
//可是,首先使用ThreadLocal的所有意义在于避免不一样的线程看到相同的实例

3.2建立具备Supplier接口实现的ThreadLocal。

//为Java ThreadLocal变量指定初始值的第二种方法是使用其内部的静态工厂方法(Supplier),将Supplier接口实现做为参数传递。
//这个Supplier实现为ThreadLocal提供初始值。
//下面是一个使用其withInitial()静态工厂方法建立ThreadLocal的示例,该方法传递一个简单的供应商实现做为参数
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
    @Override
    public String get() {
        return String.valueOf(System.currentTimeMillis());
    }
});
//Java8 lambda表达式的写法
ThreadLocal threadLocal = ThreadLocal.withInitial(
        () -> { return String.valueOf(System.currentTimeMillis()); } );
//还能够更短
ThreadLocal threadLocal3 = ThreadLocal.withInitial(
        () -> String.valueOf(System.currentTimeMillis()) );

4.延迟设置ThreadLocal的值

//在某些状况下,您不能使用设置初始值的标准方法。例如,您可能须要一些在建立ThreadLocal变量时不可用的配置信息。
//在这种状况下,能够延迟设置初始值。
//下面是如何在Java ThreadLocal上惰性地设置初始值的示例
public class MyDateFormatter {

    private ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();

    public String format(Date date) {
        SimpleDateFormat simpleDateFormat = getThreadLocalSimpleDateFormat();
        return simpleDateFormat.format(date);
    }
    
    private SimpleDateFormat getThreadLocalSimpleDateFormat() {
        SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
        if(simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            simpleDateFormatThreadLocal.set(simpleDateFormat);
        }
        return simpleDateFormat;
    }
}
//注意format()方法是如何调用getThreadLocalSimpleDateFormat()方法来获取Java SimpleDatFormat实例的。
//若是在ThreadLocal中没有设置SimpleDateFormat实例,那么就会在ThreadLocal变量中建立并设置一个新的SimpleDateFormat。
//一旦线程在ThreadLocal变量中设置了本身的SimpleDateFormat,就会对该线程使用相同的SimpleDateFormat对象继续前进。
//但只是为了那条线。每一个线程建立本身的SimpleDateFormat实例,由于它们不能看到在ThreadLocal变量上设置的其余实例。

//SimpleDateFormat类不是线程安全的,所以多个线程不能同时使用它。为了解决这个问题,
//上面的MyDateFormatter类为每一个线程建立一个SimpleDateFormat,
//所以调用format()方法的每一个线程将使用本身的SimpleDateFormat实例。

5.多线程状况下使用 ThreadLocal

若是您计划从传递给Java线程池或Java ExecutorService的任务内部使用Java ThreadLocal,请记住,您不能保证哪一个线程将执行您的任务。
可是,若是您所须要的只是确保每一个线程使用某个对象的本身的实例,那么这不是问题。而后,您能够将Java ThreadLocal与线程池或ExecutorService一块儿使用。多线程

5.1 完整的ThreadLocal 示例

public class ThreadLocalExample {

    public static void main(String[] args) {
        MyRunnable sharedRunnableInstance = new MyRunnable();

        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);

        thread1.start();
        thread2.start();

        thread1.join(); //wait for thread 1 to terminate
        thread2.join(); //wait for thread 2 to terminate
    }

}
//
public class MyRunnable implements Runnable {

    private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

    @Override
    public void run() {
        threadLocal.set( (int) (Math.random() * 100D) );

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }

        System.out.println(threadLocal.get());
    }
}

//这个例子建立了一个MyRunnable实例,
//它被传递给两个不一样的线程。两个线程都执行run()方法,所以在ThreadLocal实例上设置不一样的值。
//若是对set()调用的访问是同步的,而且它不是ThreadLocal对象,那么第二个线程就会覆盖第一个线程设置的值。
//可是,由于它是ThreadLocal对象,因此两个线程不能看到彼此的值。所以,它们设置和获取不一样的值。

6.可继承(Inheritable) ThreadLocal

InheritableThreadLocal类是ThreadLocal的子类。与每一个线程在ThreadLocal中都有本身的值不一样,
InheritableThreadLocal将对值的访问权授予一个线程和该线程建立的全部子线程。下面是一个完整的Java InheritableThreadLocal示例:dom

public class InheritableThreadLocalBasicExample {

    public static void main(String[] args) {

        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        InheritableThreadLocal<String> inheritableThreadLocal =
                new InheritableThreadLocal<>();

        Thread thread1 = new Thread(() -> {
            System.out.println("===== Thread 1 =====");
            threadLocal.set("Thread 1 - ThreadLocal");
            inheritableThreadLocal.set("Thread 1 - InheritableThreadLocal");

            System.out.println(threadLocal.get());
            System.out.println(inheritableThreadLocal.get());

            Thread childThread = new Thread( () -> {
                System.out.println("===== ChildThread =====");
                System.out.println(threadLocal.get());
                System.out.println(inheritableThreadLocal.get());
            });
            childThread.start();
        });

        thread1.start();

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("===== Thread2 =====");
            System.out.println(threadLocal.get());
            System.out.println(inheritableThreadLocal.get());
        });
        thread2.start();
    }
}


//这个例子建立了一个普通的Java ThreadLocal和一个Java InheritableThreadLocal。
//而后,示例建立一个线程,该线程设置ThreadLocal和InheritableThreadLocal的值——而后建立一个子线程,
//该子线程访问ThreadLocal和InheritableThreadLocal的值。只有InheritableThreadLocal的值对子线程是可见的。
//最后,示例建立了第三个线程,该线程也尝试访问ThreadLocal和InheritableThreadLocal——可是它看不到第一个线程存储的任何值
//输出值:
===== Thread 1 =====
Thread 1 - ThreadLocal
Thread 1 - InheritableThreadLocal
===== ChildThread =====
null
Thread 1 - InheritableThreadLocal
===== Thread2 =====
null
null
相关文章
相关标签/搜索