作用:设计线程安全的一种技术。 在使用多线程的时候,如果多个线程要共享一个非线程安全的对象,常用的手段是借助锁来实现线程的安全。线程安全隐患的前提是多线程共享一个不安全的对象 ,那么有没有办法让线程之间不共享这个对象,就像你和我,每个人都有自己的一个苹果,你吃你的,我吃我的,你我互不干涉,来达到线程的安全?有 !在java.lang包下有一个类叫ThreadLocal<T>,让线程之间各自持有自己的对象T。

来看看 来自多线程编程指南的一个案列,的使用方式和其工作原理

/*
授权声明:
本源码系《Java多线程编程实战指南(核心篇)》一书(ISBN:978-7-121-31065-2,以下称之为“原书”)的配套源码,
欲了解本代码的更多细节,请参考原书。
本代码仅为原书的配套说明之用,并不附带任何承诺(如质量保证和收益)。
以任何形式将本代码之部分或者全部用于营利性用途需经版权人书面同意。
将本代码之部分或者全部用于非营利性用途需要在代码中保留本声明。
任何对本代码的修改需在代码中以注释的形式注明修改人、修改时间以及修改内容。
本代码可以从以下网址下载:
https://github.com/Viscent/javamtia
http://www.broadview.com.cn/31065
*/
package io.github.viscent.mtia.ch6; import java.io.IOException;
import java.io.PrintWriter; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; /**
* 该类可能导致内存泄漏!
* @author Viscent Huang
*/
@WebServlet("/memoryLeak")
public class ThreadLocalMemoryLeak extends HttpServlet {
private static final long serialVersionUID = 4364376277297114653L;
final static ThreadLocal<Counter> counterHolder = new ThreadLocal<Counter>() {
@Override
protected Counter initialValue() {
Counter tsoCounter = new Counter();
return tsoCounter;
}
}; @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doProcess(req, resp);
try (PrintWriter pwr = resp.getWriter()) {
pwr.printf("Thread %s,counter:%d",
Thread.currentThread().getName(),
counterHolder.get().getAndIncrement());
}
} private void doProcess(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
counterHolder.get().getAndIncrement();
// 省略其他代码
}
} // 非线程安全
class Counter {
private int i = 0;
public int getAndIncrement() {
return i++;
}
}

ThreadLocalMemoryLeak 通过ThreadLocal<Counter> counterHolder 来实现每个线程有各自的对象Counter,那么是只能用来实现每个线程都有自己Counter呢 ?先看看ThreaLocal的源码 ,ThreadLocal的内部成员结构:通过查看ThreadLocal的方法,我们可以往ThreadLocal中设置成员T,只有set方法,那么跟踪下set方法的源码先

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null; //原来threadLocals 就是ThreadLocal的内部类。

可以看到set方法内通过Thread获取到当前线程,然后操作当前的线程,获取到ThreadLocal的内部类ThreadLocalMap,然后往这个Map中把值Value set进去 ,所以现在知道了。设置的值Value 就是往当前线程的ThreadLocalMap中设置值,这样的话当然只有跟当前线程有关和别的线程无关了 ,所有达到了各自的线程有各自的对象T了。看看ThreadLocalMap的set方法又做了什么 :

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();
}

一看原来value 值放在了Entry中 。而每一个Entry都是ThreadLocalMap成员变量 tab数组的一个值。Entry又是ThreadLocalMap的内部类。,内部类Entry继承了垃圾回收机制的引用类,用来判断是否实例对象可回收。

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

所以现在可以看明白ThreadLocal<T>持有对象 T,最后变成当前线程 currentThread持有Entry对象实例,Entry持有对象T。

但是案列中似乎并没有看到对ThreadLocal 对象set方法的操作 ,只看到 创建对象重写了方法initialValue。 看方法名意思是初始化持有对象T。但是代码并没有对ThreadLocalMap去赋值,而是返回持有对象T,通过查看引用原来在setInittialValue 方法中有调用initialVlaue,setInitialValue又在get方法中调用 ,看看get方法的源码

private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

原来在get获取对象T的时候 ,如果原先没有set值进去 ,执行initialValu方法,并把值设置到当前线程持有对象ThreadLocalMap中。

使用ThreadLocal的优劣势:

  1.因为没有使用到线程锁,可以避免线程上下文 的切换 。

  2.造成内存的泄露

  3.退化与数据错乱 。

  更多的详细参考数据《java多线程实战指南》

看看线程特有对象ThreadLocal的更多相关文章

  1. 【Java线程安全】 — ThreadLocal

    [用法] 首先明确,ThreadLocal是用空间换时间来解决线程安全问题的,方法是各个线程拥有自己的变量副本. 既然如此,那么是涉及线程安全,必然有一个共享变量,我给大家声明一个: public c ...

  2. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  3. 线程本地变量ThreadLocal源码解读

      一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...

  4. 线程本地变量ThreadLocal

    一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...

  5. 线程池与Threadlocal

    线程池与Threadlocal 线程池: 线程池是为了使线程能够得到循环的利用,线程池里面养着一些线程,有任务需要使用线程的时候就往线程池里抓线程对象出来使用.线程池里的线程能够重复使用,所以在资源上 ...

  6. 并发之线程封闭与ThreadLocal解析

    并发之线程封闭与ThreadLocal解析 什么是线程封闭 实现一个好的并发并非易事,最好的并发代码就是尽量避免并发.而避免并发的最好办法就是线程封闭,那什么是线程封闭呢? 线程封闭(thread c ...

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

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

  8. Java并发(二十):线程本地变量ThreadLocal

    ThreadLocal是一个本地线程副本变量工具类. 主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的 ...

  9. 不同线程不能获取其他线程设置的ThreadLocal里面的值

    背景: 最近在项目用到了ThreadLocal,存放一些值.起线程异步获取ThreadLocal中的值,得到null.这是由于,ThreadLocal.get()会获取当前线程的一个map对象,以Th ...

随机推荐

  1. Linux之间的文件传输方式

    大数据集群经常涉及文件拷贝,我在学习大数据时总结了几种方式 三台主机:192.168.10.100.192.168.10.101.192.168.10.102有一个一样的用户:swcode 做过映射关 ...

  2. GDI绘制Winform工作流组件、具有独立图层的增删处理、防PPT效果

    最近接了个小项目10K.用了2个下班时间写完,共花费了6-7个小时完成.如有同类需求的可以与本人联系,QQ:120772981 功能目标: 需要写一个仿PPT画泳道图的组件.之前写过工作流的组件,其实 ...

  3. CTF入门学习3->Web通信基础

    Web安全基础 01 Web通信 这个部分重点介绍浏览器与Web服务器的详细通信过程. 01-00 URL协议 只要上网访问服务器,就离不开URL. URL是什么? URL就是我们在浏览器里输入的站点 ...

  4. [nowcoder5669E]Eliminate++

    枚举$a_{i}$并判断是否可行,有以下结论:若$a_{i}$可以留下来,一定存在一种合法方案使得$a_{i}$仅参与最后若干次合并,且第一次参与合并前左右都不超过2个数 证明:将大于$a_{i}$的 ...

  5. LifseaOS 悄然来袭,一款为云原生而生的 OS

    作者:黄韶宇.初扬 审核&校对:溪洋.海珠 编辑&排版:雯燕 LifseaOS 在刚刚过去的云栖大会上,一款新的 Linux Base 操作系统悄悄发布,它就是 LifseaOS(Li ...

  6. 洛谷 P3711 - 仓鼠的数学题(多项式)

    洛谷题面传送门 提供一种不太一样的做法. 假设要求的多项式为 \(f(x)\).我们考察 \(f(x)-f(x-1)\),不难发现其等于 \(\sum\limits_{i=0}^na_ix^i\) 考 ...

  7. JS 执行上下文的一次理解

    执行上下文 执行上下文概念 当代码运行时,会产生一个对应的执行环境,在这个环境中,变量会被事先提出来(变量提升),代码从上往下开始执行,就叫做执行上下文. 注:在定义变量是未直接赋值,使用默认值 un ...

  8. C/C++ Qt TabWidget 实现多窗体创建

    在开发窗体应用时通常会伴随分页,ToolBar组件可以实现顶部工具栏菜单,每一个ToolBar组件关联到一个TabWidget组件的Tab标签内,这样我们就可以实现一个复杂的多窗体分页结构,此类结构也 ...

  9. 毕业设计之dns搭建:

    [apps@dns_sever ~]$ sudo yum install -y bind [apps@dns_sever ~]$ sudo vim /etc/named.conf // // name ...

  10. QQ空间技术架构之深刻揭秘

    QQ空间技术架构之深刻揭秘 来源: 腾讯大讲堂  发布时间: 2012-05-17 17:24  阅读: 7822 次  推荐: 4   [收藏]   QQ 空间作为腾讯海量互联网服务产品,经过近七年 ...