Java并发编程:线程封闭和ThreadLocal详解

什么是线程封闭

当访问共享变量时,往往需要加锁来保证数据同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程中访问数据,就不需要同步了。这种技术称为线程封闭。在Java语言中,提供了一些类库和机制来维护线程的封闭性,例如局部变量和ThreadLocal类,本文主要深入讲解如何使用ThreadLocal类来保证线程封闭。

理解ThreadLocal类

ThreadLocal类能使线程中的某个值与保存值的对象关联起来,它提供了get、set方法,这些方法为每个使用该变量的线程保存一份独立的副本,因此get总是set当前线程的set最新值。

首先我们来看个例子,这个例子来自于http://www.cnblogs.com/dolphin0520/p/3920407.html

public class Test1 {

    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();


    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }

    public long getLong() {
        return longLocal.get();
    }

    public String getString() {
        return stringLocal.get();
    }
    public static void main(String[] args) throws InterruptedException {
        final Test1 test = new Test1();


        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());


        Thread thread1 = new Thread(() -> {
            test.set();
            System.out.println(test.getLong());
            System.out.println(test.getString());
        });
        thread1.start();
        thread1.join();

        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

运行该程序,代码输出的结果为:

1 main 10 Thread-0 1 main

从这段代码可以看出在mian线程和thread1线程确实都保存着各自的副本,它们的副本各自不干扰。

ThreadLocal源码解析

来从源码的角度来解析ThreadLocal这个类,这个类存放在java.lang包,这个类有很多方法。

它内部又个ThreadLocalMap类,主要有set()、get()、setInitialValue 等方法。

首先来看下set方法,获取当前Thread的 map,如果不存在则新建一个并设置值,如果存在设置值,源码如下:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

跟踪createMap,可以发现它根据Thread创建来一个ThreadLocalMap。

  void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

t.threadLocals为当前线程的一个变量,也就是ThreadLocal的数据都是存放在当前线程的threadLocals变量里面的,由此可见用ThreadLocal存放的数据是线程安全的。因为它对于不同的线程来,使用ThreadLocal的set方法都会根据线程判断该线程是否存在它的threadLocals成员变量,如果没有就建一个,有的话就存下数据。

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap为ThreadLocal的一个内部类,源码如下:

 static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

在使用ThreadLocal的get方法之前一定要先set,要不然会报空指针异常。还有一种方式就是在初始化的时候调用initialValue()方法赋值。改造下之前的例子,代码如下:

public class Test2 {

    ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){

        @Override
        protected Long initialValue() {
            return Thread.currentThread().getId();
        }
    };
    ThreadLocal<String> stringLocal = new ThreadLocal<String>(){
        @Override
        protected String initialValue() {
            return Thread.currentThread().getName();
        }
    };

    public long getLong() {
        return longLocal.get();
    }

    public String getString() {
        return stringLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final Test2 test = new Test2();



        System.out.println(test.getLong());
        System.out.println(test.getString());


        Thread thread1 = new Thread(() -> {

            System.out.println(test.getLong());
            System.out.println(test.getString());
        });
        thread1.start();
        thread1.join();

        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

运行该程序,代码输出的结果为:

1 main 10 Thread-0 1 main

ThreadLocal常用的使用场景

通常讲JDBC连接保存在ThreadLocal对象中,每个对象都有属于自己的连接,代码如下:

private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
    public Connection initialValue() {
       return DriverManager.getConnection(DB_URL);
    }
};

public static Connection getConnection() {
    return connectionHolder.get();
}

参考资料

《Java并发编程实战》

《深入理解JVM》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏涤生的博客

WeakHashMap垃圾回收原理

WeakHashMap自然联想到的是HashMap。确实,WeakHashMap与HashMap一样是个散列表,存储内容也是键值对。与HashMap类似的功能就...

782
来自专栏Java与Android技术栈

Scala学习笔记(四) 类的初步Scala 类相关总结

终于来到面向对象的地方了,虽说函数式编程是 Scala 的特性,让人们觉得 Scala 是更好的 Java。但是在架构层面上一直提倡着:小处用函数式编程,大处用...

952
来自专栏快乐八哥

Javascript中数组的sort()和reverse()方法

Javascript开发中,数组是用到最多一个内容。同时数组的方法不多,下面介绍在数组排序中用到的2个常用方法。sort()方法和reverve()方法。 so...

1926
来自专栏Java编程技术

ThreadLocal原理探究

多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候,为了保证线程安全,一般需要使用者在访问共享变量的时候进行适当的同步,...

881
来自专栏守候书阁

实例感受-es6的常用语法和优越性

前几天,用es6的语法重写了我的一个代码库,说是重写,其实改动的并不多,工作量不大。在重写完了的时候,就个人总结了一下es6常用的一些常用的语法和比es5优越的...

713
来自专栏好好学java的技术栈

谁问你ThreadLocal,就把这篇文章丢给他

ThreadLocal是线程局部变量,所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量。在各个Java web的各种框架中Thre...

1062
来自专栏ml

虚函数中构造函数的调用顺序

1 /*曾经有段时间一直被构造函数中的虚函数所困扰,现在通过自己重新学习了一遍,标注一下容易忘记的知识*/ 2 #include<iostream> 3 ...

3028
来自专栏Java编程技术

ThreadLocal原理探究

多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候,为了保证线程安全,一般需要使用者在访问共享变量的时候进行适当的同步,...

992
来自专栏闪电gogogo的专栏

【数据结构(C语言版)系列四】 串

串(或字符串)是由零个或多个字符组成的有限序列,一般记为 s = 'a1a2...an',s为串名。子串在主串中的位置以子串的第一个字符在主串中的位置来表示。

621
来自专栏一枝花算不算浪漫

[Java Collection]List分组之简单应用.

2875

扫码关注云+社区