什么是线程封闭

当访问共享变量时,往往需要加锁来保证数据同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程中访问数据,就不需要同步了。这种技术称为线程封闭。在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();}


更多相关文章

  1. 100道Java并发和多线程基础面试题大集合(含解答),这波面试稳了~
  2. golang 发送post请求 其body中json对象使用变量作为value
  3. Java多线程打辅助的三个小伙子
  4. 深入学习 Java 线程池
  5. Java 多线程:volatile 变量、happens-before 关系及内存一致性
  6. 到底多少线程算是线程数太多?
  7. 多线程之死锁就是这么简单

随机推荐

  1. Android中ListView以及数组适配器(ArrayA
  2. android 列表框
  3. 直播软件源码android ui LinearLayout中
  4. Android ExpandableListView同时显示多种
  5. CheckBox设置颜色和自定义样式
  6. 如何在G1上安装非android market的apk
  7. Android 双进程守护
  8. Android RadioButton
  9. android绑定服务方法使用
  10. android studio 遇到的问题