作用:设计线程安全的一种技术。 在使用多线程的时候,如果多个线程要共享一个非线程安全的对象,常用的手段是借助锁来实现线程的安全。线程安全隐患的前提是多线程共享一个不安全的对象 ,那么有没有办法让线程之间不共享这个对象,就像你和我,每个人都有自己的一个苹果,你吃你的,我吃我的,你我互不干涉,来达到线程的安全?有 !在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. feign微服务调用携带浏览器信息(header、cookie)

    import feign.RequestInterceptor; import feign.RequestTemplate; import org.apache.commons.collections ...

  2. jenkins安装与配置---window,mis包直接安装

    https://my.oschina.net/aibinxiao/blog/1457218 Jenkins在Windows下的安装与配置   已经在https://jenkins.io/下载好了Win ...

  3. 菜鸡的Java笔记 第十六 - java 引用传递

    referenceDelivery    引用传递是整个java 的精髓,也是所有初学者最难学的地方        引用的本质:同一块堆内存可以被不同的栈内存所指向    下面通过三道程序来进行引用传 ...

  4. Java设计模式之(二)——工厂模式

    1.什么是工厂模式 Define an interface for creating an object,but let subclasses decide which class toinstant ...

  5. [loj2863]组合动作

    先用两次猜出第一个字符,后面就不会出现这个字符了 (我们假设这个字符是c0,其余三种字符分别是c1.c2和c3) ,然后考虑已知s的前i个字符(不妨就s),来推出后面的字符 询问:s+c1和s+c2, ...

  6. [noi706]Sabotage

    先可以将所有出度为0的节点连向一个点,然后问题变为求到这个点的必经之点这其实是一道模板题,因为有一个东西叫做支配树容易发现一个点的必经之点都是一条链,其实可以把这条链上最浅的点作为这个点的父亲,那么一 ...

  7. 基于echarts 24种数据可视化展示,填充数据就可用,动手能力强的还可以DIY(演示地址+下载地址)

    前言 我们先跟随百度百科了解一下什么是"数据可视化 [1]". 数据可视化,是关于数据视觉表现形式的科学技术研究. 其中,这种数据的视觉表现形式被定义为,一种以某种概要形式抽提出来 ...

  8. Python的数据解析

  9. 使用protobuf-java-format包 JsonFormat转Json部分默认值字段消失问题

    使用protobuf-java-format包 JsonFormat转Json部分默认值字段消失问题 1.产生的bug XXXXXXXXRequest.Builder request = XXXXXX ...

  10. [GZOI2017]配对统计

    发现我们可以在\(O(n)\)里很多处理出至多\(2n\)对好对. 然后转化成二维偏序. 然后想怎么做怎么做:排序+BIT,莫队都行.