我们再介绍一个在多线程环境中经常使用的类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_WebView_全屏

    WebView(网络视图)能加载显示网页,能够将其视为一个浏览器. 它使用了WebKit渲染引擎加载显示网页,实现WebView有下面两种不同的方法: 第一种方法的步骤: 1.在要Activity中实 ...

  2. NET:Checkboxlist,Dropdownlist 加入ToolTip说明

    ToolTip属性: ToolTip 类 (System.Windows.Controls)‎ 表示创建弹出项的控件.该弹出项可显示界面中元素的相关信息.命名空间: System.Windows.Co ...

  3. python xml.etree.ElementTree解析xml文件获取节点

    <?xml version = "1.0" encoding = "utf-8"?> <root> <body name=&quo ...

  4. 09-spring学习-资源访问接口

    目标: 1,掌握Resource接口的使用. 2,掌握ResourceLoader接口的使用. 3,掌握各种资源数据的读取操作. 具体内容: 要想进行资源读取操作,首先想到IO包中提供的操作类. 但是 ...

  5. hibernate 多对一关联

    (转自尚学堂教学内容)   注解多对一: package com.bjsxt.hibernate; import javax.persistence.Entity; import javax.pers ...

  6. Java Jaxb JavaBean与XML互转

    1.Jaxb - Java Arcitecture for XML Binding 是业界的一个标准,是一项能够依据XML Schema产生Java类的技术. Jaxb2.0是Jdk1.6的组成部分. ...

  7. (七)Oracle学习笔记—— 游标

    1.游标简介 游标用来处理从数据库中检索的多行记录(使用SELECT语句).利用游标,程序可以逐个地处理和遍历一次检索返回的整个记录集. 为了处理SQL语句,Oracle将在内存中分配一个区域,这就是 ...

  8. 编译ORBSLAM2 build_ros.sh,实现kinect2在ROS环境下运行ORBSLAM2

    //編譯ORBSLAM2 build_ros.sh參考:“http://www.cnblogs.com/bigzhao/p/6635770.html”1.source ~/.bashrc出現問題:ct ...

  9. C# 递归查找文件夹下所有文件和子文件夹的所有文件

    方法实现 public class DirectoryAllFiles { static List<FileInformation> FileList = new List<File ...

  10. Linux下Tomcat 8080 端口被占用的解决办法

    希望可以帮助你们 一,停止tomcat 并执行#netstat -an|grep 8080   查看发现有许多80端口进程在里面 二,执行# lsof -i :8080|grep -v "P ...