我们再介绍一个在多线程环境中经常使用的类ThreadLocal,它是java为解决多线程程序的并发问题提供了一种新的方向,使用这个ThreadLocal类可以帮助开发者很简单地编写出简洁的程序,并且是线程安全的。ThreadLocal很容易让人误解,认为是一个“本地线程”,其实ThreadLocal并不是一个Thread,而是Thread的一个局部变量。

当使用ThreadLocal变量时,ThreadLocal为每个使用这个变量的线程提供不同的变量副本,所以每一个线程改变的只是自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量。我们先来了解一下ThreadLocal的接口方法

ThreadLocal类提供的接口方法比较简单,很容易使用:

public void set(Object value)   该方法设置当前线程局部变量的值。

public Object get()   该方法返回当前线程所对应的线程局部变量的值。

public void remove()  该方法删除当前线程局部变量的值,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。这这里需要说明的是,当线程结束后,该线程的所有局部变量将自动被垃圾回收器(GC)回收,所以显式调用该方法清除线程的某些局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected Object initialValue()   返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,ThreadLocal中的缺省实现直接返回一个null。

下面举一个列子来说明一下他的用法。

 

public class TestThreadLocal {

static class myRunnable implements Runnable {
int index;
public myRunnable(int i){
index =i;
}
public void run() {
myThreadLocal.set("Thread:"+ index);
System.out.println(myThreadLocal.get());

}
}

public static ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();

public static void main(String[] args){
Thread thread1 = new Thread(new myRunnable(1));
Thread thread2 = new Thread(new myRunnable(2));
thread1.start();
thread2.start();
System.out.println(myThreadLocal.get());
}
}

执行的结果如下:

Thread:1
Thread:2
null

thread 1 返回Thread:1的结果,thread2返回Thread:2,最后一个null是main线程的执行结果,因为 main线程没有设置自己的局部变量。

ThreadLocal的实现原理

在介绍ThreadLocal变量之前,我们先来看一个Thread的内部变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

这个变量就是线程用来存储自己的ThreadLocal变量的空间,他的类型 ThreadLocal.ThreadLocalMap,作用相当于一个hashmap,对这种数据类型的操作是不需要加锁的,因为只有一个线程(也就是线程自己)才可能操作到自己的变量。

我们先看ThreadLocal源代码是来解析两个比较重要的函数:set和get

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);//拿到本地线程map

if (map != null)

map.set(this, value);//map存在时,添加value

else

createMap(t, value);//map不存在时,创建map并且添加value

}

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t); //拿到本地线程map

if (map != null) {//map存在时,或者key

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value;

}

return setInitialValue();

}

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

void createMap(Thread t, T firstValue) {

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

从代码上看,ThreadLocal函数都是先获得当前线程的引用,然后得到线程局部变量,最后以ThreadLocal作为key,设置或者获取value,线程本地变量是类型为ThreadLocalMap的变量,ThreadLocalMap相当于一种哈希表,有兴趣的可以看一下。整个过程没有任何同步操作,因此性能很高。

ThreadLocal与其它同步机制的比较

ThreadLocal和线程同步机制都是为了解决多线程访问变量冲突的问题,同步机制是通过对象的锁机制保证同一时间只有一个线程访问变量,此时该变量是多个线程共享的,使用同步机制要求用户分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等复杂的问题,程序设计和编写难度相对比较大。而ThreadLocal则从另一个角度来解决多线程的并发访问,它会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突,因为每一个线程都拥有自己的变量副本,它只操作自己的变量副本,从而也就没有必要对该变量进行同步了。

当然ThreadLocal并不能替代同步机制,两者面向的问题不同。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源,这样当然不需要对多个线程进行同步了。所以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序,使程序更加易读、简洁。

InheritableThreadLocal

通常线程还需要维护另外一个变量 inheritableThreadLocals

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null

当创建一个新的线程时,线程会从父线程哪里复制一份数据。也就是是说,子线程会享有父线程的数据,下面的代码摘自Thread.init函数,子线程创建时,将会调用。

if (parent.inheritableThreadLocals != null)

this.inheritableThreadLocals =

ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

请看它的getMap函数,显然操作的是inheritableThreadLocals变量

ThreadLocalMap getMap(Thread t) {

return t.inheritableThreadLocals;

}

这时所有的操作都是针对Thread的inheritableThreadLocals,这种操作也是线程安全的。

我们来举一个例子来说明InheritableThreadLocal的说法

public class TestInheritableThreadLocal {

static class myRunnable implements Runnable {
int index;
public myRunnable(int i){
index =i;
}
public void run() {
//
System.out.println("thread " + index + "-" + myThreadLocal.get());
myThreadLocal.set("Thread:"+ index);
System.out.println("thread " + index + "-" + myThreadLocal.get());

}
}

public static InheritableThreadLocal<String> myThreadLocal = new InheritableThreadLocal<String>();

public static void main(String[] args){

myThreadLocal.set("parent main thread");
Thread thread1 = new Thread(new myRunnable(1));
Thread thread2 = new Thread(new myRunnable(2));
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

System.out.println("main thead -" + myThreadLocal.get());
}
}

结果如下:

thread 1-parent main thread
thread 2-parent main thread
thread 2-Thread:2
thread 1-Thread:1
main thead -parent main thread

线程1和线程2会共享main线程的数据,子线程也可以修改自己的副本,修改后,线程1得到的结果Thread:1,线程2得到结果Thread:2

而父线程(main)的变量时不会噶边的,因为子线程的修改只作用于自己的变量空间,不会作用到父线程。

概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而不受影响。

ThreadLocal分析的更多相关文章

  1. ThreadLocal分析总结:

    1.ThreadLocal 是什么 它是一个数据结构,像 HashMap,可保存 "key : value" 键值对:ThreadLocal 有一个内部类ThreadLocalMa ...

  2. JDK之ThreadLocal分析

    ThreadLocal是在是Thread的一个局部变量,今天我来分析了一下这个类 先看ThreadLocal的set方法 public void set(T value) { Thread t = T ...

  3. Netty源码分析-- ThreadLocal分析(九)

    为了更好地探讨Netty的内存模型,后面会用到,这里我还是决定跟大家一起看下ThreadLocal和FastThreadLocal的源码,有的时候我们在看源码的时候会一层层的遇到很多之前没有看过的内容 ...

  4. ThreadLocal ——android消息机制handler在非主线程创建not called Looper.prepare() 错误的原因

    引用自:https://www.jianshu.com/p/a8fa72e708d3 引出: 使用Handler的时候,其必须要跟一个Looper绑定.在UI线程可直接初始化Handler来使用.但是 ...

  5. 并发基础(十) 线程局部副本ThreadLocal之正解

      本文将介绍ThreadLocal的用法,并且指出大部分人对ThreadLocal 的误区. 先来看一下ThreadLocal的API: 1.构造方法摘要 ThreadLocal(): 创建一个线程 ...

  6. Java 多线程--ThreadLocal Timer ExecutorService

    ThreadLocal /** * ThreadLocal:每个线程自身的存储本地.局部区域 * @author xzlf * */ public class ThreadLocalTest01 { ...

  7. 面经手册 · 第12篇《面试官,ThreadLocal 你要这么问,我就挂了!》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 说到底,你真的会造火箭吗? 常说面试造火箭,入职拧螺丝.但你真的有造火箭的本事吗,大 ...

  8. 支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <支持JDK19虚拟线程的web框架>系列 ...

  9. ThreadLocal 工作原理、部分源码分析

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

随机推荐

  1. Android 底部TabActivity(1)——FragmentActivity

    先看看效果图: 第一篇Tab系列的文章首先实现这样的风格的底部Tab:背景条颜色不变,我们是用了深灰的颜色,图标会发生对应的变化.当选中某个标签后该标签的背板会由正常的颜色变为不正常,哈哈,是变为加深 ...

  2. 图片转成base64, base64转成图片

    1.我们在看代码时经常在img或css背景图片中看到: src=”data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAMAAAAOusbgA ...

  3. HBuilder 打包 vue-cli 构建的 APP

    1.在打包之前需要修改一个地方,那就是config->index.js文件,修改assetsPublicPath: '/'为assetsPublicPath: './',截图如下 上面文件改好后 ...

  4. 使用Python爬虫爬取网络美女图片

    代码地址如下:http://www.demodashi.com/demo/13500.html 准备工作 安装python3.6 略 安装requests库(用于请求静态页面) pip install ...

  5. Sphinx-简介及原理

    1.Sphinx简介 是一款基于SQL的高性能全文检索引擎(还不支持NoSQL), 主要优点有: 1).创建和重建索引迅速 2).大数据量时检索速度较快 3).为很多脚本语言设计了检索API(如PHP ...

  6. CentOS7关闭默认防火墙启用iptables防火墙

    CentOS 7.0默认使用的是firewall作为防火墙,这里改为iptables防火墙步骤. 1.关闭firewall: systemctl stop firewalld.service #停止f ...

  7. C# 上传文件大小限制设置

    (1)在web.comfig文件中添加一个httpRuntime主键 <httpRuntime executionTimeout="90" maxRequestLength= ...

  8. java 重载和多态的区别

    虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.而对象的隐藏成员--虚拟函数表指针是在运行期--也就是构造函数被调用时进行初始化的,这是实现多态的关键. http ...

  9. TI博客文章-4-20mA电流环路发送器入门

    TI博客文章-4-20mA电流环路发送器入门http://bbs.21ic.com/forum.php?mod=viewthread&tid=1610834&fromuid=10995 ...

  10. Navicat for MySQL再谈之无奈之下还是去安装Navicat Premium

    不多说,直接上干货! 首先,Navicat for MySQL没有查看数据库属性. 其次,没有这个功能多和强大,在走过一段弯路之后,果断放弃Navicat for MySQL,而使用Navicat P ...