1、简介:

类ThreadLocal<T>,为变量提供了线程本地化副本。对于用ThreadLocal维护的变量,当前线程中的副本不同于它在其他线程中的副本,每个线程通过ThreadLocal提供的get、set等方法来独立维护自己的变量副本。当多线程环境中的变量使用ThreadLocal维护时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。

ThreadLocal实例在类中通常作为静态私有化类变量。

2、ThreadLocal接口:

public T get(); //返回被维护变量的变量的线程本地化的副本值
public void set(T value); //将当前线程中维护的本地化副本值设置为value
public void remove(); //移除当前线程中被维护变量的值 protected T initialValue(); //提供线程本地化对象的初始值

3、适用性:

ThreadLocal适用于为每个线程都提供一个固定的初始状态的情形,每个线程使用自己的本地化对象来完成自己的操作。

4、Demo:

对非线程安全对象的改造:

      public class TopicDao{
//一个非线程安全的变量
private Connection conn; public void addTopic(){
conn = DriverManager.getConnection(); //在多线程环境中使用非线程安全变量
Statement stat = conn.createStatement(); }
}

在使用JDBC来操作数据库时,Connection对象提供了所连接的数据库的表、支持的SQL语句等信息,但是Connection本身并不是一个线程安全变量,用于多线程环境中可能会出现问题。

改造: 使用ThreadLocal来为每个线程维护一个conn变量的线程本地化副本。

      public class TopicDao{
//使用ThreadLocal来保存Connection变量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
//如果connThreadLocal没有本线程对应的Connection,创建一个新的Connection,
//并将其保存到线程本地变量中
if(connThreadLocal.get() == null ){
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
//直接返回线程本地变量
return connThreadLocal.get();
}
} public void addTopic(){
//从ThreadLocal中获取线程对象
Statement stat = getConnection().createStatement();
}
}

ThreadLocal使用方式

由例子中也可看出,ThreadLocal实例通常作为类的私有静态成员,并且提供一个静态方法来返回ThreadLocal所维护的线程本地化对象(该例中未conn)的值,同时在该静态方法中为每个线程提供该线程本地化对象的初始值。

5、ThreadLocal.java源文件:

     /**
* ThreadLocal内部包含一个用数组实现的哈希表,用来存储对应到每个线程的局部对象的值
* 其中,ThreadLocal对象担当key,实际通过threadLocalHashCode值来进行检索
*/
public class ThreadLocal<T> {
/**
* 存储用于确定哈希表的键值
*/
private final int threadLocalHashCode = nextHashCode(); /**
* 返回在当前线程中的线程局部对象的值,
* 若线程局部对象对于当前线程没有值,则被初始化微 initialValue方法的返回值
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
} return setInitialValue();
} /**
* 获取当前线程对应的ThreadLocalMap对象
* 若存在,则将当前线程与T类型变量value按照名值对的方式放置到ThreadLocalMap中;
* 若不存在,则创建当前ThreadLocalMap对象,并放入
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//以当前线程对象为key,设置当前局部对象的值
map.set(this, value);
else
createMap(t, value);
} /**
* 移除当前线程中所保存的本地化对象的值
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//从ThreadLocalMap中移除对象
m.remove(this);
} /**
* 返回当前线程关联的ThreadLocalMap对象
* 查看Thread源码可知,每个Thread都含有ThreadLocal.ThreadLocalMap类型的threadLocals变量
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
} /**
* 创建当前线程关联的ThreadLocalMap对象
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
} /**
* ThreadLocalMap是一个定制的只能用来存储线程局部对象的哈希映射表
*
* 使用数组来作为哈希表存储结构
*
* 其中数组中元素类型为Entry,Entry为指向ThreadLocal的弱引用的子类,并且含有Object变量来维护与当前ThreadLocal对象关联的变量值
*
*/
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal> {
/**
* 维护与当前ThreadLocal相关联的变量值
*/
Object value; Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
} //哈希表的初始大小
private static final int INITIAL_CAPACITY = 16; /**
* 哈希表存储的数组
*
* 其中数组中元素类型为Entry,Entry为指向ThreadLocal的弱引用的子类,并且含有Object变量来维护与当前ThreadLocal对象关联的变量值
*/
private Entry[] table; /**
* ThreadLocalMap使用延迟初始化,当我们需要向ThreadLocalMap中放元素时,才会初始化它
*/
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //产生哈希值
table[i] = new Entry(firstKey, firstValue); //初始时,使用ThreadLocal.threadLocalHashCode作为哈希表的哈希值 size = 1; //设定ThreadLocalMap中元素个数
setThreshold(INITIAL_CAPACITY);
} /**
* 设置于当前key所关联的线程本地化对象的值
*
* 涉及到哈希表的冲突处理
*/
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
} if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
} /**
* 删除ThreadLocal对应的线程本地化对象的值
*/
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
}
}

根据ThreadLocal.java中列出的源码,可以走一遍ThreadLocal是如何进行set、get、remove操作的,可以加深对ThreadLocal对象的理解和其内部工作机制。

注:查看Thread源码可知,每个Thread类中都含有ThreadLocal.ThreadLocalMap类型的threadLocals变量(初始值为null)。

ThreadLocal学习的更多相关文章

  1. ThreadLocal学习记录

    ThreadLocal简介 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的 ...

  2. Java ThreadLocal 学习

    同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式. 而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多 ...

  3. ThreadLocal学习笔记

    首先,ThreadLocal是Java语言提供的用于支持线程局部变量的标准实现类.很多时候,ThreadLocal与Synchronized在功能上有一定的共性,都可以用来解决多线程环境下线程安全问题 ...

  4. ThreadLocal 学习

    JDK 1.2版本就已经提供了java.lang.ThreadLocal.其为多线程程序的并发问题提供了一种新的思路.使用该工具类可以简洁地编写出优美的多线程程序. 当使用ThreadLocal维护变 ...

  5. ThreadLocal学习资料

    下面的这一段代码运行起来,就会发生线程安全问题: 启动两个线程,同时去修改 name 属性值. package com.liwei.thread; /** * 下面的代码演示了线程安全发生的由来 * ...

  6. java并发编程学习: ThreadLocal使用及原理

    多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么ThreadLocal可以很方便的帮你做到这一点. 先来看一下示例: package yjmyz ...

  7. 2015年11月26日 Java基础系列(三)ThreadLocal类初级学习

    序,ThreadLocal类是为了解决多线程的安全问题.线程安全的意思也就是说每个线程操作自己的变量,不要对其他线程的值造成影响. 在很多情况下,ThreadLocal比直接使用synchronize ...

  8. 深入学习ThreadLocal原理

    上文我们学习了ThreadLocal的基本用法以及基本原理,ThreadLocal中的方法并不多,基本用到的也就get.set.remove等方法,但是其核心逻辑还是在定义在ThreadLocal内部 ...

  9. ThreadLocal<T>学习总结

    public class ThreadLocalTest { /** * @param * @Author: xdj * @Date: 2019/4/12 10:16 * @Description: ...

随机推荐

  1. Android应用程序所包含的四种组件和DDMS

    关注用户组件         Activity                               编辑文本 .玩游戏 后台进程               Service           ...

  2. 在Android应用程序使用YouTube API来嵌入视频

    在Android版YouTube播放器API使您可以将视频播放功能到你的Android应用程序.该API允许您加载和播放YouTube视频(和播放列表),并自定义和控制视频播放体验. 您可以加载或暗示 ...

  3. CentOS7使用VPN上网

    安装epel源 $ sudo yum install epel-release 安装软件包 $ sudo yum install NetworkManager-pptp NetworkManager- ...

  4. 转:HashMap的工作原理,及笔记

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...

  5. 00_ForTest

    -----该页是爬虫的测试页请忽略------- 1234545@qq.comadasdsdasdsad阿打算多少其热情为 asdasdasdasd4w5wsdvv啊实打实大 啊实打实大asdasda ...

  6. c++primer复习(六)—面向对象编程

    1 C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定,两个条件(基类引用或指针.虚函数)缺一不可 虚函数的默认实参将发生静态绑定 2 继承层次的根类一般都需要定义虚析构函数 3 任意非st ...

  7. void void*

    void类型及void指针 1.概述 许多初学者对C/C 语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误.本文将对void关键字的深刻含义进行解说,并 详述void及void指 ...

  8. 『奇葩问题集锦』function * (next){ 执行报错 SyntaxError: Unexpected token *

    这是因为  app.use(function * (){ 语句中有一个 * ,这种方式被称为generator functions ,一般写作function *(){...} 的形式,在此类func ...

  9. PHP设计模式之适配器模式

    将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本的由于接口不兼容而不能一起工作的那些类可以一起工作.应用场景:老代码接口不适应新的接口需求,或者代码很多很乱不便于继续修改,或者使用第三方类 ...

  10. kindeditor编辑器代码过滤解决方法.

    很多朋友在使用Kindeditor编辑器的时候都会遇到这样一个问题,如:给A标签加上title属性过后,浏览的时候,却神奇般地发现title属性没有了.再次切换html源代码的时候,返现编辑器将tit ...