Basic Of Concurrency(八: ThreadLocal)

499 阅读3分钟

ThreadLocal类在java中能够让变量只能被相同的线程读写.那么即使两个线程同时访问相同的代码中的相同变量,也会产生两个变量副本,变量副本分别仅对本线程可见. 在java中较为典型的应用莫过于ServletHttpServletRequest了.

创建一个ThreadLocal

done like this:

private ThreadLocal myThreadLocal = new ThreadLocal();

以上代码实例化了一个ThreadLocal, ThreadLocal在同一个线程中,仅需要被实例化一次即可.多个线程运行这段代码时,每个线程都会创建一个自己的ThreadLocal副本,且仅对本线程可见.当两个线程往ThreadLocal设置不同值时,它们见不到对方设置的值.

访问ThreadLocal

ThreadLocal创建完成后,可以通过set()来存储值.

done like this:

myThreadLocal.set("test var")

可以通过get()来取得原先存储的值.

done like this:

String myVar = (String)myThreadLocal.get();

调用get()返回之前存储的值,调用set()方法,可以传递一个Object作为存储值.

ThreadLocal泛型化

我们可以创建一个泛型化的ThreadLocal,这样在调用get()就不用进行类型强转了.

done like this:

private ThreadLocal myThreadLocal = new ThreadLocal<String>();

这样我们可以直接通过set()来存储目标类型值,通过get()来取得目标类型值:

myThreadLocal.set("text var");
String myVar = myThreadLocal.get();

设置ThreadLocal默认值

我们知道通过调用set()来存储值仅会对当前线程可见,对其他线程不可见.有时候我们需要设置对所有线程可见的默认值.

这时我们可以继承ThreadLocal类并覆盖initialValue().当然也可以用使用匿名内部类的方式.

done like this:

private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override protected String initialValue() {
        return "This is the initial value";
    }
}; 

现在所有线程在没有调用set()的前提下,都能通过get()来取得默认值.

完整实例

public class ThreadLocalDemo {
    private ThreadLocal<Integer> myThreadLocal = new ThreadLocal();
    private AtomicInteger counter = new AtomicInteger();
    private Runnable runnable = () -> {
        Thread curThread = Thread.currentThread();
        int count = counter.getAndIncrement();
        System.out.println(curThread.getName() + ": set the val(" + count + ")");
        myThreadLocal.set(count);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(curThread.getName() + ": get the val(" + myThreadLocal.get() + ")");
    };

    public static void main(String[] args) {
        ThreadLocalDemo tld = new ThreadLocalDemo();
        IntStream.range(1, 3)
                .mapToObj(i -> new Thread(tld.runnable, "Thread-" + i))
                .map(Thread::new)
                .forEach(Thread::start);
    }
}

我们创建一个ThreadLocalDemo用于示范ThreadLocal的使用.首先我们在内部实例化了ThreadLocal用于存储int类型值,然后实例化AtomicInteger用于产生不同的整数,再然后就是定义我们的Runnable.Runnable中先后调用ThreadLocalset()get().set()get()中间停顿了2s钟,用于预留时间让线程对ThreadLocal存储值进行覆盖,确保两个线程都已经调用ThreadLocalset()后再调用get().预期结果是每个线程通过get()取得都是之前通过set()存储的值.

执行结果:

Thread-0: set the val(0)
Thread-1: set the val(1)
Thread-0: get the val(0)
Thread-1: get the val(1)

由结果可以看出ThreadLocal存储的值确实仅对本线程可见,若换成普通的变量结果应该是类似如下所示:

Thread-0: set the val(0)
Thread-1: set the val(1)
Thread-0: get the val(1)
Thread-1: get the val(1)

InheritableThreadLocal

InheritableThreadLocalThreadLocal的子类.与ThreadLocal不同的是InheritableThreadLocal存储值允许当前线程创建的所有子线程访问.

该系列博文为笔者复习基础所著译文或理解后的产物,复习原文来自Jakob Jenkov所著Java Concurrency and Multithreading Tutorial

上一篇: volatile关键字
下一篇: 线程通讯