转载:https://www.cnblogs.com/chengxiao/p/6152824.html

总结:

  1. 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪
  2. 用法:在切面中生成 loggerTag,如果 loggerTag有值则直接取,如果没有值,则自动生成一个,

    在同一个请求过程中,loggerTag是一样的,如果遇到异步执行时,需要通过参数将loggerTag向下传递。 如果遇到系统间调用,需要在另一个切面中先判断loggerTag有值没,没值的话需要自动生成一个向下传递,在切面中方法结束时需要移除本地线程变量

谈谈Java中的ThreadLocal

ThreadLocal介绍&跳出误区

看看源码

线程独享变量?

ThreadLocal介绍&跳出误区

  ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一起,为每一个线程维护一个独立的变量副本。通过ThreadLocal可以将对象的可见范围限制在同一个线程内。

跳出误区

  需要重点强调的的是,不要拿ThreadLocal和synchronized做类比,因为这种比较压根就是无意义的!sysnchronized是一种互斥同步机制,是为了保证在多线程环境下对于共享资源的正确访问。而ThreadLocal从本质上讲,无非是提供了一个“线程级”变量作用域,它是一种线程封闭(每个线程独享变量)技术,更直白点讲,ThreadLocal可以理解为将对象的作用范围限制在一个线程上下文中,使得变量的作用域为“线程级”。

  没有ThreadLocal的时候,一个线程在其声明周期内,可能穿过多个层级,多个方法,如果有个对象需要在此线程周期内多次调用,且是跨层级的(线程内共享),通常的做法是通过参数进行传递;而ThreadLocal将变量绑定在线程上,在一个线程周期内,无论“你身处何地”,只需通过其提供的get方法就可轻松获取到对象。极大地提高了对于“线程级变量”的访问便利性。

来看个简单的例子

  假设我们要为每个线程关联一个唯一的序号,在每个线程周期内,我们需要多次访问这个序号,这时我们就可以使用ThreadLocal了.(当然下面这个例子没有完全体现出跨层级跨方法的调用,理解就可以了)

package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

/**
  • Created by chengxiao on 2016/12/12.

    */

    public class ThreadLocalDemo {

    public static void main(String []args){

    for(int i=0;i<5;i++){

    final Thread t = new Thread(){

    @Override

    public void run(){

    System.out.println("当前线程:"+Thread.currentThread().getName()+",已分配ID:"+ThreadId.get());

    }

    };

    t.start();

    }

    }

    static class ThreadId{

    //一个递增的序列,使用AtomicInger原子变量保证线程安全

    private static final AtomicInteger nextId = new AtomicInteger(0);

    //线程本地变量,为每个线程关联一个唯一的序号

    private static final ThreadLocal<Integer> threadId =

    new ThreadLocal<Integer>() {

    @Override

    protected Integer initialValue() {

    return nextId.getAndIncrement();//相当于nextId++,由于nextId++这种操作是个复合操作而非原子操作,会有线程安全问题(可能在初始化时就获取到相同的ID,所以使用原子变量

    }

    };
    </span><span style="color: #008000;">//</span><span style="color: #008000;">返回当前线程的唯一的序列,如果第一次get,会先调用initialValue,后面看源码就了解了</span>
    <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">int</span><span style="color: #000000;"> get() {
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> threadId.get();
    }

    }

    }

执行结果,可以看到每个线程都分配到了一个唯一的ID,同时在此线程范围内的"任何地点",我们都可以通过ThreadId.get()这种方式直接获取。

当前线程:Thread-4,已分配ID:1
当前线程:Thread-0,已分配ID:0
当前线程:Thread-2,已分配ID:3
当前线程:Thread-1,已分配ID:4
当前线程:Thread-3,已分配ID:2 

看看源码

 set操作,为线程绑定变量

 public void set(T value) {
Thread t = Thread.currentThread();//1.首先获取当前线程对象
ThreadLocalMap map = getMap(t);//2.获取该线程对象的ThreadLocalMap
if (map != null)
map.set(this, value);//如果map不为空,执行set操作,以当前threadLocal对象为key,实际存储对象为value进行set操作
else
createMap(t, value);//如果map为空,则为该线程创建ThreadLocalMap
}

可以看到,ThreadLocal不过是个入口,真正的变量是绑定在线程上的。

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;//线程对象持有ThreadLocalMap的引用
}

下面给是Thread类中的定义,每个线程对象都拥有一个ThreadLocalMap对象

    ThreadLocal.ThreadLocalMap threadLocals = null;

现在,我们能看出ThreadLocal的设计思想了:

1.ThreadLocal仅仅是个变量访问的入口;

2.每一个Thread对象都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用;

3.ThreadLocalMap以当前的threadlocal对象为key,以真正的存储对象为value。get时通过threadlocal实例就可以找到绑定在当前线程上的对象。

乍看上去,这种设计确实有些绕。我们完全可以在设计成Map<Thread,T>这种形式,一个线程对应一个存储对象。ThreadLocal这样设计的目的主要有两个:

  一是可以保证当前线程结束时相关对象能尽快被回收;二是ThreadLocalMap中的元素会大大减少,我们都知道map过大更容易造成哈希冲突而导致性能变差。

我们再来看看get方法

 public T get() {
Thread t = Thread.currentThread();//1.首先获取当前线程
ThreadLocalMap map = getMap(t);//2.获取线程的map对象
if (map != null) {//3.如果map不为空,以threadlocal实例为key获取到对应Entry,然后从Entry中取出对象即可。
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();//如果map为空,也就是第一次没有调用set直接get(或者调用过set,又调用了remove)时,为其设定初始值
}
setInitialValue
  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;
}

initialValue方法,默认是null,访问权限是protected,即允许重写。

 protected T initialValue() {
return null;
}

谈到这儿,我们应该已经对ThreadLocal的设计目的及设计思想有一定的了解了。

线程独享变量?

  还有一个会引起疑惑的问题,我们说ThreadLocal为每一个线程维护一个独立的变量副本,那么是不是说各个线程之间真正的做到对于对象的“完全自治”而不对其他线程的对象产生影响呢?其实这已经不属于对于ThreadLocal的讨论,而是你出于何种目的去使用ThreadLocal。如果我们为一个线程关联的对象是“完全独享”的,也就是每个线程拥有一整套的新的 栈中的对象引用+堆中的对象,那么这种情况下是真正的彻底的“线程独享变量”,相当于一种深度拷贝,每个线程自己玩自己的,对该对象做任何的操作也不会对别的线程有任何影响。

  另一种更普遍的情况,所谓的独享变量副本,其实也就是每个线程都拥有一个独立的对象引用,而堆中的对象还是线程间共享的,这种情况下,自然还是会涉及到对共享资源的访问操作,依然会有线程不安全的风险。所以说,ThreadLocal无法解决线程安全问题。

  所以,需不需要完全独享变量,进行完全隔离,就取决于你的应用场景了。可以想象,对象过大的时候,如果每个线程都有这么一份“深拷贝”,并发又比较大,对于服务器的压力自然是很大的。像web开发中的servlet,servlet是线程不安全的,一请求一线程,多个线程共享一个servlet对象;而早期的CGI设计中,N个请求就对应N个对象,并发量大了之后性能自然就很差。

  ThreadLocal在spring的事务管理,包括Hibernate的session管理等都有出现,在web开发中,有时会用来管理用户会话 HttpSession,web交互中这种典型的一请求一线程的场景似乎比较适合使用ThreadLocal,但是需要特别注意的是,由于此时session与线程关联,而tomcat这些web服务器多会采用线程池机制,也就是说线程是可复用的,所以在每一次进入的时候都需要重新进行set,或者在结束时及时remove。

作者:
dreamcatcher-cx

出处:
<http://www.cnblogs.com/chengxiao/>

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在页面明显位置给出原文链接。

	</div>
<div class="postDesc">posted @ <span id="post-date">2016-12-14 00:02</span> <a href="https://www.cnblogs.com/chengxiao/">dreamcatcher-cx</a> 阅读(<span id="post_view_count">22089</span>) 评论(<span id="post_comment_count">4</span>) <a href="https://i.cnblogs.com/EditPosts.aspx?postid=6152824" rel="nofollow">编辑</a> <a href="#" onclick="AddToWz(6152824);return false;">收藏</a></div>
</div>
posted on
2019-07-13 10:22 
duende99 
阅读(...) 
评论(...) 
编辑 
收藏

markdown_highlight();

var allowComments = true, cb_blogId = 348919, cb_blogApp = 'duende99', cb_blogUserGuid = '8bbcf7bd-e8c4-e611-845c-ac853d9f53ac';
var cb_entryId = 11179655, cb_entryCreatedDate = '2019-07-13 10:22', cb_postType = 1;
loadViewCount(cb_entryId);
loadSideColumnAd();

var commentManager = new blogCommentManager();
commentManager.renderComments(0);

var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];

googletag.cmd.push(function () {
googletag.defineSlot("/1090369/C1", [300, 250], "div-gpt-ad-1546353474406-0").addService(googletag.pubads());
googletag.defineSlot("/1090369/C2", [468, 60], "div-gpt-ad-1539008685004-0").addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});

fixPostBody();
deliverBigBanner();
setTimeout(function() { incrementViewCount(cb_entryId); }, 50); deliverAdT2();
deliverAdC1();
deliverAdC2();
loadNewsAndKb();
loadBlogSignature();
LoadPostCategoriesTags(cb_blogId, cb_entryId); LoadPostInfoBlock(cb_blogId, cb_entryId, cb_blogApp, cb_blogUserGuid);
GetPrevNextPost(cb_entryId, cb_blogId, cb_entryCreatedDate, cb_postType);
loadOptUnderPost();
GetHistoryToday(cb_blogId, cb_blogApp, cb_entryCreatedDate);

Threadlocal线程本地变量理解的更多相关文章

  1. Java并发机制(4)--ThreadLocal线程本地变量(转)

    个人理解: 说明:看了博客园中大神写的ThreadLocal的详解,感觉还是有些迷糊,下面用自己的理解简单描述下ThreadLocal的机制(难免有误): 1.首先ThreadLocal用于存储对应线 ...

  2. ThreadLocal 线程本地变量 及 源码分析

    ■ ThreadLocal 定义 ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量 ...

  3. ThreadLocal线程本地变量

    首先说明ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递,这样处理后,能够优雅的解决一些实际问题,比如hibernate中的OpenSessi ...

  4. 深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类

    ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多. 可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内 ...

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

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

  6. 线程本地变量ThreadLocal

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

  7. 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理

    前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...

  8. 线程本地变量ThreadLocal (耗时工具)

    线程本地变量类 package king; import java.util.ArrayList; import java.util.List; import java.util.Map; impor ...

  9. 线程本地变量ThreadLocal (耗时工具)【原】

    线程本地变量类 package king; import java.util.ArrayList; import java.util.List; import java.util.Map; impor ...

随机推荐

  1. 2018-2019-2 20165215《网络对抗技术》Exp8 Web基础

    目录 实验内容 基础问题回答 实验步骤 (一)Web前端HTML (二) Web前端javascipt (三)Web后端:MySQL基础:正常安装.启动MySQL,建库.创建用户.修改密码.建表 (四 ...

  2. LeetCode 130. 被围绕的区域(Surrounded Regions)

    题目描述 给定一个二维的矩阵,包含 'X' 和 'O'(字母 O). 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充. 示例: X X X X X O O X X X ...

  3. Git常用操作详细说明

    1.1  git的安装,没有什么特殊的,直接下一步就OK了: 1.2  搜索找到Git,会出现两个git,一个是Bash(命令行),一个GUI(页面),一般用 Bash,GUI页面比较老: 1.3  ...

  4. leetcode324 摆动排序II

      1. 首先考虑排序后交替插入 首尾交替插入,这种方法对于有重复数字的数组不可行: class Solution { public: void wiggleSort(vector<int> ...

  5. Hadoop : MapReduce中的Shuffle和Sort分析

    地址 MapReduce 是现今一个非常流行的分布式计算框架,它被设计用于并行计算海量数据.第一个提出该技术框架的是Google 公司,而Google 的灵感则来自于函数式编程语言,如LISP,Sch ...

  6. 编写javad代码实现使用Scanner从键盘读取一行输入,去掉其中重复字符, 打印出不同的那些字符

    package com.loaderman.test; import java.util.HashSet; import java.util.Scanner; public class Test2 { ...

  7. 七十七:flask.Restful之flask-Restful参数验证

    flask_restful插件提供了reqparse来做类似WTForms的验证功能来校验数据,add_argument可以指定这个字段的名字.数据类型等1.default:默认值,若没有传入此次参数 ...

  8. Tomcat远程调试参数

    Linux: 关闭防火墙 vim catalina.sh export CATALINA_OPTS="-server -Xdebug -Xnoagent -Djava.compiler=NO ...

  9. Python之Numpy:线性代数/矩阵运算

    当你知道工具的用处,理论与工具如何结合的时候,通常会加速咱们对两者的学习效率. 零 numpy 那么,Numpy是什么? NumPy(Numerical Python) 是 Python 语言的一个扩 ...

  10. 使用rman备份将rac环境恢复到单实例

    使用rman备份将rac环境恢复到单实例 rac环境 [oracle@rac02 ~]$ cat /etc/hosts 127.0.0.1 localhost localhost.localdomai ...