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

    一: C++工程和Gameplay框架 GameInstance 它适合放置独立于关卡的信息,比如说显示UI. GameMode 表示游戏玩法, 包含游戏进行的规则和胜利条件等等信息,游戏模式是和关卡 ...

  2. Java 多线程 - 总结概述

    概述 菜鸟教程: Java 给多线程编程提供了内置的支持. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 多线程是多任务的一种特别的形式,但多线程 ...

  3. 『与善仁』Appium基础 — 12、Appium的安装详解

    目录 (一)Appium server安装 方式一:(桌面方式:推荐) 1.Appium Desktop下载 2.Appium Desktop安装 3.Appium Desktop使用 方式二:(No ...

  4. 深入理解Redis 数据结构—简单动态字符串sds

    Redis是用ANSI C语言编写的,它是一个高性能的key-value数据库,它可以作用在数据库.缓存和消息中间件.其中 Redis 键值对中的键都是 string 类型,而键值对中的值也是有 st ...

  5. 解决 IDEA 2021.2.3 新建maven项目只有两个archetype项目模板的问题

    最近把我的 IDEA 版本更新到 2021.2.3 了,发生了一个比较有意思的问题,做个小小的记录 思路分析 在新的 IDEA 中配置完Maven之后,想要创建Maven项目的时候没有自动加载arch ...

  6. [CSP-S2019] 树的重心

    也是一个很不错的题目. 考虑我们钦定重心为根. 那么就有这样一个性质: 如果我们断的边在一个点的子树里,则这个点不会成为断边后的分裂树的根. 所以我们就只要考虑断边在子树外即可. 那么我们设\(x\) ...

  7. 洛谷 P7163 - [COCI2020-2021#2] Svjetlo(树形 dp)

    洛谷题面传送门 神仙级别的树形 dp. u1s1 这种代码很短但巨难理解的题简直是我的梦魇 首先这种题目一看就非常可以 DP 的样子,但直接一维状态的 DP 显然无法表示所有情况.注意到对于这类统计一 ...

  8. CF 585 E Present for Vitalik the Philatelist

    CF 585 E Present for Vitalik the Philatelist 我们假设 $ f(x) $ 表示与 $ x $ 互质的数的个数,$ s(x) $ 为 gcd 为 $ x $ ...

  9. 洛谷 P3688 - [ZJOI2017]树状数组(二维线段树+标记永久化)

    题面传送门 首先学过树状数组的应该都知道,将树状数组方向写反等价于前缀和 \(\to\) 后缀和,因此题目中伪代码的区间求和实质上是 \(sum[l-1...n]-sum[r...n]=sum[l-1 ...

  10. awk多文件处理时的分隔符?

    目录 问题来源 解决方法 问题来源 我有两个文件,一个是plink过滤后得到的.fam文件(空格分隔):另一个是样本对应关系文件(tab分隔). 文件1: 文件2: 两个文件匹配,awk常规操作.这里 ...