彻底理解ThreadLocal、ITL、TTL

article/2025/10/9 10:48:20

懒人改变了世界,简化了世界,却隐瞒了世界的真相.

大家好,今天跟大家剖析一下ThreadLocal.

文章目录

  1. 痛苦的回忆
  2. 抛几个问题
  3. 前奏铺垫
  4. 正片详解

痛苦的回忆

不知道大家有没有面向JDBC编程的经历.如果有的话,可以回想一下那是一种怎样的体验;如果没有,也可以假装有这样的经历,然后可以构想一下那是一种什么体验.
我体验过.太痛苦了!

由于要面向对象编程,我们的业务逻辑要写在Service层,然后一个Service内经常会用到多个dao层方法.
因为我们每次操作都要打开数据库连接,为了不经常打开/关闭数据库连接,我们需要在service层打开数据库连接,然后把数据库的connection传到dao层.service层处理完之后,再统一关闭数据库连接.
数据库连接是稀缺资源嘛.
注意:我们做了两件事:
1在service层的方法都要打开数据库连接,finally里面关闭连接.
2每个dao层接口,都要加一个connection参数.
每一个Service类的方法,都要写这么多程式化的代码,想想这个操作过程,再对比现在大家都在使用的简洁易用的SpringBoot,有多痛苦不言而喻了.

虽然SpringBoot以及这些类似的框架,简化了我们的工作量,但是我们需要知道,程序实际执行的时候,没一个打开连接、关闭连接的操作都是不能省略的,那框架是如何实现省略程式化开发,专注业务代码的呢?
动态修改字节码的AOP+ThreadLocal.
这里我就不给大家做实现了,相信大多数人也知道怎么玩.也欢迎你留言区一起讨论.

当然,ThreadLocal在做项目开发时,也会有很多应用.
比如,经过软负载的服务,有一些是存在状态的,当然实现方式有很多啦,假如使用请求携带token的方式,那么可以考虑ThreadLocal,这样在这次请求中,无论是验签,还是其他地方使用.利用ThreadLocal,你都可以少传递一个参数.

抛几个问题

我们是如何使用ThreadLocal的?
ThreadLocal如何工作的?
系统中的异步,基本都会依赖线程池,threadLocal和线程池可以同时使用么?
什么情况下会有内存泄漏的问题?
使用线程池+threadLocal时,主线程和子线程可以实现数据隔离么?
主线程和子线程可以实现数据共享么?
ThreadLocalMap内,为什么key做成弱引用,而value做成强引用?
InheritableThreadLocal 关于父子线程,ThreadLocal是如何处理的?

前奏铺垫

1、本文研究范围仅限Java8.
2、ThreadLocalMap
ThreadLocal内部维护了一个ThreadLocalMap静态内部类.ThreadLocalMap内部又维护了一个Entry静态内部类.静态内部类的作用,无疑就是对数据进行封装呗,不对外暴露.
我之前看源码时,经常会见到内部类 VS 静态内部类,因此我对此进行了较为详细的阐述:
https://blog.csdn.net/liu765023051/article/details/100884988

ThreadLocalMap提供了一种为ThreadLocal定制的高效实现,并且自带一种基于弱引用的垃圾清理机制。
ThreadLocalMap提供一个名为table的Entry数组.
Entry类内,使用ThreadLocal对象作为key,key为弱引用,变量值存储为value,value为强引用.

在这里插入图片描述

如图. 可知:ThreadLocalMap其实就是提供了一个名为table的Entry数组.该table为强引用,entry内key为ThreadLocal对象,且为弱饮用;entry内value为存储变量值,且为强引用.

3、ThreadLocalMap VS HashMap

在数据结构中,哈希是非常高效的一种思路.应用也非常广泛.数据库引擎有哈希索引,分库分表可以利用哈希进行,负载均衡都会有一种策略叫哈希策略等等.哈希随处可见.
哈希在JDK里面,实现时也是信手拈来.尽管实现方式有所不同,但是思路都是一样的,哈希算法有两个关键点:如何做散列的哈希算法、冲突后如何处理的冲突算法.
下面对比下ThreadLocalMap VS HashMap的冲突算法.

HashMap的数据结构比较清楚吧,数组+单链表.哈希冲突时,通过单链表解决哈希冲突.
即散列冲突时,遍历链表,以定位数据.

ThreadLocalMap是如何做散列的呢?
ThreadLocalMap使用一个名为table的Entry数组,但是Entry没有使用单链表,而是使用了线性探测的方式,解决哈希冲突.
所以,这个Entry数组,其实是一个逻辑环.
(线性探测自己去了解一下哦,此处不细讲)

环形示意图:

环形逻辑:

/*** 负载因子为2/3,初始容量默认16,设置扩容阈值.*/
private void setThreshold(int len) {threshold = len * 2 / 3;
}/*** 获取下一个元素索引*/
private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);
}/*** 获取上一个元素索引*/
private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);
}

4、调试ThreadLocal源代码时,尽量要创建新线程,在新线程内调试.原因很简单.线程对象里面有ThreadLocalMap对象的引用.
Main函数是主线程,运行时,会初始化主线程需要的ThreadLocalMap信息.对你Debug算是一种干扰.

正片详解

什么是ThreadLocal?
顾名思义,ThreadLocal可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,各个线程之间是不会影响的。
它提供了一种将可变数据,通过每个线程有自己的独立副本 ,从而实现线程封闭的机制。

ThreadLocal大致的实现思路是怎样的?
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。
ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里设置值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

其实理解ThreadLocal有一个弯,也是ThreadLocal有意思的地方:
一个线程内,可以使用多个ThreadLocal;多个线程间完全隔离.

怎么理解呢?
一个线程内,可以使用多个ThreadLocal.
一个线程对象内部,维护了一个ThreadLocalMap对象,该对象内 存储了该线程内所有创建的ThreadLocal.
ThreadLocalMap对象内,使用ThreadLocal对象做Key,相应的待存储的变量为value.这样就实现了一个线程内,可以使用多个ThreadLocal变量.这多个ThreadLocal变量通过一个ThreadLocalMap维护.

多个线程间完全隔离.
一个线程对象内部,维护了一个ThreadLocalMap对象,即 多个线程,会分别维护他们自己的ThreadLocal对象.通过对象隔离,实现数据隔离.

ThreadLocal是如何工作的?
线程对象thread,持有一个ThreadLocalMap对象,ThreadLocalMap对象内持有entry数组,名为table,
entry内key就是ThreadLocal对象,value为 相应的变量value.

所以,可以通过ThreadLocal对象,获取value 的值,这个实现过程等价于该线程对象所保存的值.

在这里插入图片描述

如图, 该线程对象ThreadObjA,成员变量threadLocals持有强引用,即ThreadLocalMap,
threadLocals跟ThreadLocalMap是聚合关系,注意是聚合,不是组合.关于这组关系,本文不细讲,以后讲设计模式的时候,碰到这组关系时,好好讲讲.
ThreadLocalMap对象持有名为table的Entry数组,每个entry又用弱引用ThreadLocalRef1做key,变量作为value.

此外,该线程对象内,又创建了threadLocalRef1和threadLocalRef2两个对象.
易知:线程执行完毕,方法出栈.该线程内的变量threadLocalRef1和threadLocalRef2,以及线程对象的成员变量threadLocals,它们的强引用均消失.
那么垃圾回收时,从GCRoot分析可达性时,该ThreadLocalMap和ThreadLocalObj对象率先失去强引用.
先分析ThreadLocalMap,垃圾回收时优先会被回收,其次是名为table的entry数组,然后是value值.
再看ThreadLocal对象,随着线程对象消亡,ThreadLocal对象也会优先失去强引用.仅仅被entry持有弱引用.
什么是弱引用呢?简单了解下,在垃圾回收器线程扫描内存区域时,一旦发现弱引用对象,不管当前内存空间是否足够,均会回收它的内存.但是,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现哪些具有若引用的对象.
总结一下:
Entry的key回收:就是失去了强引用的ThreadLocal对象,只剩下Entry的一条弱引用,随时可能会被回收掉.
Entry的value回收:因为存在一条从当前线程对象连接过来的一条强引用. 只有当前thread结束以后, 当前线程对象就不会存在栈中,强引用断开, 当前线程对象, ThreadLocalMap, value将全部被GC回收.

先总结一下这个例子
创建一个ThreadLocal对象,其实有两条引用指向了ThreadLocal对象.一个外引用,一个内引用.
在这个例子中,这个线程执行方式时,创建了两个ThreadLocal对象,其引用分别是:threadLocalRef1和threadLocalRef2.这两个引用可以看做是外引用.
内引用是指Thread–>ThreadLocalMap–>Entry–>Value这一条链.如图所示.共有weakRef1和weakRef2两个弱引用.

这是正常情况,对吧.那如果发生了异常线程并未退出呢?
如果线程执行时发生了异常,导致自己并未退出,那么该线程持有的成员变量threadLocals,以及内部创建的对象引用ThreadLocalRef,它们拥有的是强引用,均不会释放,那么该ThreadLocal在GC线程回收时,就会逃过GCroot追溯.
如果这样的线程越变越多,且内部的ThreadLocal及其相关的对象,每次都能比过gc线程的回收操作,那么就会导致了内存泄漏.

那线程池+ThreadLocal呢,真的会导致内存泄漏么?
极少情况,存在内存泄漏的可能.

1、正常情况下,会出现脏读.
如果是线程池在执行.我们知道,线程池内的线程,是会重复执行任务的.即线程池内某个线程在执行完某个任务之后,没有立即消亡,而是去执行另一个任务.
这时如果直接执行get方法,那么它就会读到上一个任务中的set的值,即脏数据.
注意,这时该线程对象所持有的强引用并未释放,即线程对象内的ThreadLocal对象,仍然持有强引用.
所以线程池+ThreadLocal,使用不当的话,会导致脏读.

所以,在使用线程池时,使用完ThreadLocal,应该调用threadLocal.remove方法,或者设置threadLocal的变量 为null.便于gc线程回收.
同时,有可能会有脏读,但是不会出现内存泄漏.

2、那在使用线程池时,使用ThreadLocal,什么时候会导致内存泄漏呢?
正常情况下,线程对象都会被gc线程回收(同时相关threadLocalMap、entry、key、value等对象都会被回收),那这时就不会出现内存泄漏.
但是在ThreadLocal设为null,但是线程尚未结束,且要存活一段时间,那这时慢慢会达到一种:作为Entry的key的ThreadLocal对象已经回收了,为null了;而value却随线程存活而一直存在.
此时,我们假设该线程曾创建了10个ThreadLocal对象,那么此时,就会有10个Entry对象. 此时每个Entry对象key为null,value不为空.

此时,如果我们项目里面有了很多很多这样的线程,就有可能发生内存泄漏.

此时有几个注意点:
1、如果key不设置成null,那么就不构成内存泄漏了,因为这种情况,栈内引用持有着ThreadLocal的强引用,会被认为是该ThreadLocal还在被正常使用;
2、此时,这样很多很多的线程,同时 不使用threadLocal的方法.即:get、set、remove方法.
因为ThreadLocal开发者在value的可用性与内存泄漏的可能性之间做了权衡 ,即分别在get、set、remove方法内,对key为null的Entry进行了处理.即如果你调用了任意一个threadLocal对象的get、set、remove方法,就会检查相应key为null的Entry,并进行清理.
3、更多情况,ThreadLocalMap内会一直累加之前存储过的KV,即过多无意义的Entry对象.少数使用不当的情况会发生脏读.

在ThreadLocalMap中,Entry中的key,即ThreadLocal为什么要使用弱引用呢?
这就比较好理解了.
因为ThreadLocal在没有外部强引用来引用它的时候,那么ThreadLocal可以被GC线程回收.
如果Entry中的key即,ThreadLocal为强引用,那么该ThreadLocal对象,只有在ThreadLocalMap、table都被回收了之后,才有可能被回收.即ThreadLocalMap不会回收,ThreadLocal永远不会被gc线程回收.
所以,此处设计成弱引用,提前了ThreadLocal的可gc时间.

那好,现在ThreadLocal使用完了,执行了threadLocal==null操作,那么该ThreadLocal就只剩下这个一个内部entry的弱引用了,可以随时被gc线程回收了.
ThreadLocal被回收完之后,那么此时就会出现一个Entry中key为null,value却存在的情况.
如果该线程生活周期很长,那么ThreadLocalMap对象一定一直存在.因为该线程对象内部的持有ThreadLocalMap的强引用.这些key为null的entry就会存在一条强引用链的关系:
Thread–>ThreadLocalMap–>Entry–>Value.这条强引用链会导致Entry、value、ThreadLocalMap都不会被回收,有可能会造成内存泄漏.上面已经分析过这种情况.

那为什么Entry中的value,即变量值要使用强引用呢?
这也是JVM团队严谨的考虑吧.像上面,执行了threadLocal==null操作之后,threadLocal对象只剩下一个弱引用,可能随时被gc线程回收.
但是这时,注意这时,你的ThreadLocal使用完了,但是你的业务逻辑并未处理完,你的程序仍继续运行,并且还会使用该value值,那这时候,该value仍然可以使用.
JVM设计的工具类,考虑的角度,确实很全面(真的是把使用者当猪一样啊),不过这种理念设计出来的软件,确实才更加严谨.

OK,value使用强引用有道理.那么此处的value什么时候回收呢?
首先,Thread线程对象销毁了,那么这一个null链条肯定有机会被gc线程回收了;
然后你看源码会发现,当你调用ThreadLocal的get、set、remove方法时,它都回去检查key为null的entry.key为null时,释放value对象所占空间.
此处这样做,也是极大可能避免垃圾堆积吧.
此时,设置Entry的key=null,基本就可以保证不会造成内存泄漏了.
比较好理解吧.key=null了,ThreadLocal被回收了,只要你执行该线程上其他ThreadLocal对象的get、set、remove方法,都会去处理对应的key为null时的Entry对象,释放其空间.

线程池+ThreadLocal,推荐使用方式
不过,做好使用ThreadLocal的方式,还是在使用完之后,在finally里面调用一下threadLocal的remove方法.
主动删除entry,完全避免因ThreadLocal可能导致内存泄漏的可能性.提前threadLocal的gc时间.

InheritableThreadLocal 关于父子线程,ThreadLocal是如何处理的?

ThreadLocal声明的变量是线程私有的成员变量,每个线程都有该变量的副本,线程对变量的修改对其他线程不可见.

InheritableThreadLocal声明的变量同样是线程私有的,但是子线程可以使用同样的InheritableThreadLocal类型变量从父线程继承InheritableThreadLocal声明的变量,父线程无法拿到其子线程的。即使可以继承,但是子线程对变量的修改对父线程也是不可见的。

/* ThreadLocal values pertaining to this thread. This map is maintained

  • by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

/*

  • InheritableThreadLocal values pertaining to this thread. This map is
  • maintained by the InheritableThreadLocal class.
    */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal

ThreadLocal线程变量副本,实现线程隔离,也可以实现父子线程隔离;

InheritableThreadLocal子类,可以访问到父类ThreadLocal的变量值.即父线程修改的值,可以使用InheritableThreadLocal子类,使子线程获取到父类更新的变量;子线程只能用,修改了不会影响父线程的东西.
严谨一些的说法是 :在创建子线程时,将父线程当前存在的本地线程变量拷贝到子线程的本地线程变量中。

具体是如何操作的呢?
在创建 Thread 类对象时,将父线程中的 table 对象以复制的方式赋值给子线程的 table 数组.也就是说当子线程对象创建完毕后,子线程中的数据就是主线程中旧的数据,主线程使用新的数据时,子线程还是使用旧的数据,因为主子线程使用两个 Entry[] 对象数组各自存储自己的值.
此时需要看一下代码:
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];

for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}
}

}

第13行,调用了key.childValue方法.这里涉及了Java的值传递. 默认实现采用了浅拷贝.就是不管父线程value里面存储的是基本数据类型还是对象类型,都直接赋值到子线程对应的Entry内.

稍微解释一下:

对于对象类型来说,值的内容其实是对象的引用。当在父线程中修改对象的某一属性,由于子线程引用着相同对象,所以可以子线程可以感知到,本质上父线程和子线程在操作同一块内存地址,即同一个value。

对于基本数据类型来说,由于传递的是值,在父线程改变了数据后,子线程依旧使用的是旧的数据。
这里尤其要提 String 字符串,String 虽然不是基本数据类型,但是由于内部字符数组被 final 修饰带来的不可变型,
当父线程修改其 String 类型数据时,等于替换掉该 String 对象,而并不是修改原 String 对象的值.
所以子线程创建后,父线程值修改时,子线程内的value依旧不会发生变化。

这里的key.childValue()方法,是JDK的默认实现,在创建InheritableThreadLocal时,可以由使用者自行重写.

由的实现,可知其有局限性.父线程变量 复制到子线程的时机,是在子线程创建的时候.
InheritableThreadLocal + 线程池时,线程池中的线程拷贝的数据来自于第一个提交任务的外部线程.
即后面的外部线程向线程池中提交任务时,子线程访问的本地变量都来源于第一个外部线程,造成线程本地变量混乱.

TransmittableThreadLocal对此做了改进.在线程池模式下,也可以很好的将父线程本地变量传递下去.

TransmittableThreadLocal有哪些应用呢?官方给出了应用场景:
分布式跟踪系统 或 全链路压测(即链路打标)
日志收集记录系统上下文
Session级Cache
应用容器或上层框架跨应用代码给下层SDK传递信息

InheritableThreadLocal不支持线程池的根本原因是InheritableThreadLocal是在父线程创建子线程时复制的,由于线程池的复用机制,“子线程”只会复制一次。
不实用线程池时,没有问题.
要支持线程池中能访问提交任务线程的本地变量,其实只需要在父线程向线程池提交任务时复制父线程的上下环境,那在子线程中就能够如愿访问到父线程中的本地变量,实现本地环境变量在线程池调用中的透传这也就是TransmittableThreadLocal最本质的实现原理.

TransmittableThreadLocal是阿里实现的.具体的实现也很精妙.
我们一起来简单看一下.
TransmittableThread继承了InheritableThreadLocal,并丰富了它的功能.
InheritableThreadLocal是在子线程创建的时候,将父线程内变量,复制给子线程,所以出现了上述的问题:线程池+InheritableThreadLocal,导致变量混乱.
TransmittableThreadLocal相比于InheritableThreadLocal,做了改进:
1、TransmittableThreadLocal内部保存了父线程的上下文,即名为holder的全局静态变量.在执行get、set时,会将对应的value,维护在holder中;
2、在向线程池提交任务时,会通过重放、和恢复,帮助子线程同步主线程的变量值.

总结

本文试图站在一个大而全的角度,分析一下ThreadLocal以及相关的实现.
希望对你有所收获.

引用:
git官网:https://github.com/alibaba/transmittable-thread-local
源码:https://github.com/alibaba/transmittable-thread-local/blob/master/src/main/java/com/alibaba/ttl/TransmittableThreadLocal.java
不错的文章:https://mp.weixin.qq.com/s/a6IGrOtn1mi0r05355L5Ng


http://chatgpt.dhexx.cn/article/4NFRoa3j.shtml

相关文章

【转】线上内存溢出分析

状况描述&#xff1a; 最近项目新打的版本&#xff0c;过不了多长时间&#xff0c;项目就会挂掉。状况就是处于一种假死的状态。索引查询都很慢&#xff0c;几乎进行不了任何操作&#xff0c;慢慢卡死。 然后我们再发版时&#xff0c;只能基于之前打好的war包&#xff0c;替换或…

SQL Server数据库基础的级联删除、级联更新与三层架构之窥

一、定义&#xff1a; 级联删除是指删除包含主键值的行的操作&#xff0c;该值由其它表的现有行中的外键引用。在级联删除中&#xff0c;还删除其外键值引用删除的主键值的所有行。 级联更新是指更新主键值的操作&#xff0c;该值由其它表的现有行中的外键引用。在级联更新中&a…

正则表达式

正则表达式简介&#xff1a; 正则表达式是对字符串操作的一种逻辑公式&#xff0c;就是用事先定义好的一些特定字符、及这些特定字符的组合&#xff0c;组成一个“规则字符串”&#xff0c;这个“规则字符串”用来表达对字符串的一种过滤逻辑。   给定一个正则表达式和另一个…

SQL Server2005中触发器的运用

编写过存储过程的人&#xff0c;再编写触发器时会发现&#xff1a;他们的语法、格式是非常类似的。其实触发器就是一种特殊类型的存储过程。他们都是预编译的&#xff0c;在程序正式编译前就由编译器进行编译&#xff0c;存储在服务器端。 不过&#xff0c;触发器与一般的存储过…

关于SimpleDateFormat安全的时间格式化线程安全问题

关于SimpleDateFormat安全的时间格式化线程安全问题 2014年02月18日 16:19:40 zxh87 阅读数&#xff1a;34426 想必大家对SimpleDateFormat并不陌生。SimpleDateFormat 是 Java 中一个非常常用的类&#xff0c;该类用来对日期字符串进行解析和格式化输出&#xff0c;但如果使用…

LaTex - 插入公式 (从MathType公式编辑器导入到LaTex中)

原创 LaTex 论文排版(2): 插入公式 (从MathType公式编辑器导入到LaTex中) 2019年03月08日 09:37:40 在水一方xym 阅读数 5948 更多 分类专栏&#xff1a; LaTex 论文排版 LaTex 论文排版 版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&…

CSS总结

自从做牛腩新闻发布系统的时候&#xff0c;就开始了CSS的学习。CSS这部分知识并不是孤立的&#xff0c;它与JavaScript&#xff0c;与XML&#xff0c;与AJAX等都有着密切的关系。在制作网页的过程中&#xff0c;CSS就是充当一个化妆师的角色&#xff0c;它能够让我们制作出各式…

大型网站应用之海量数据和高并发解决方案总结

一、网站应用背景 开发一个网站的应用程序&#xff0c;当用户规模比较小的时候&#xff0c;使用简单的&#xff1a;一台应用服务器一台数据库服务器一台文件服务器&#xff0c;这样的话完全可以解决一部分问题&#xff0c;也可以通过堆硬件的方式来提高网站应用的访问性能&…

ehcache memcache redis三大缓存男高音

&#xfeff;&#xfeff; 研究使用缓存已经有一段时间了&#xff0c;今天本来想对比一下它们异同以及使用场景。然后我发现已经有前辈做了很不错的总结&#xff0c;而且这篇文章跟我也有很多共鸣。我想说的也就这些&#xff0c;所以这里就直接拿来主义了。 不过&#xff0c;还…

技术是个王八蛋,可是长得真好看

看完题目&#xff0c;请勿喷。最近的生活可能太苦逼了&#xff0c;好想吐槽一下~~~ 首先&#xff0c;先来分享一段个人特别喜欢的话&#xff1a; 透视社会依次为三个层面&#xff1a;制度、文化和技术。小到一个人&#xff0c;大到一个国家&#xff0c;一个民族&#xff0c;任…

学习,不是一件发愁的事儿

曾经&#xff0c;我有一个很幼稚的想法。有人告诉我&#xff1a;人体的细胞&#xff0c;每隔七年&#xff0c;就会大换血一次&#xff0c;经历一个大的生命周期。听完我就害怕了&#xff0c;七年&#xff1f;那七年后&#xff0c;我现在学习的所有知识&#xff0c;就全被我忘干…

记一次通过Memory Analyzer分析内存泄漏的解决过程

状况描述&#xff1a; 最近项目新打的版本&#xff0c;过不了多长时间&#xff0c;项目就会挂掉。状况就是处于一种假死的状态。索引查询都很慢&#xff0c;几乎进行不了任何操作&#xff0c;慢慢卡死。 然后我们再发版时&#xff0c;只能基于之前打好的war包&#xff0c;替换或…

数字图像处理之尺度空间理论

尺度空间(scale space)思想最早是由Iijima于1962年提出的&#xff0c;后经witkin和Koenderink等人的推广逐渐得到关注&#xff0c;在计算机视觉领域使用广泛。 尺度空间理论的基本思想是&#xff1a;在图像信息处理模型中引入一个被视为尺度的参数&#xff0c;通过连续变化尺度…

为什么要用高斯核来生成尺度空间?

信号的尺度空间刚提出是就是通过一系列单参数、宽度递增的高斯滤波器将原始信号滤波得到到组低频信号。那么有一个疑问就是&#xff0c;除了高斯滤波之外&#xff0c;其他带有参数t的低通滤波器是否也可以用来生成一个尺度空间呢&#xff1f; 但翻看资料得知国外诸多学者都已经…

【高分论文密码】大尺度空间模拟预测与数字制图教程

详情点击链接&#xff1a;【高分论文密码】大尺度空间模拟预测与数字制图 一&#xff0c;R语言空间数据及数据挖掘关键技术 1、R语言空间数据及应用特点 1)R语言基础与数据科学 2)R空间矢量数据 3)R栅格数据 2、R语言空间数据挖掘关键技术 二&#xff0c;R语言空间数据高…

尺度空间及SIFT

尺度空间方法的基本思想是&#xff1a;在视觉信息处理模型中引入一个被视为尺度的参数&#xff0c;通过连续变化尺度参数获得不同尺度下的视觉处理信息&#xff0c;然后综合这些信息以深入地挖掘图像的本质特征。尺度空间方法将传统的单尺度视觉信息处理技术纳入尺度不断变化的…

【高分论文密码】大尺度空间模拟预测与数字制图

大尺度空间模拟预测和数字制图技术和不确定性分析广泛应用于高分SCI论文之中&#xff0c;号称高分论文密码。大尺度模拟技术可以从不同时空尺度阐明农业生态环境领域的内在机理和时空变化规律&#xff0c;又可以为复杂的机理过程模型大尺度模拟提供技术基础。在本次培训中&…

尺度空间理论与图像金字塔(二)

SIFT简介 整理一下方便阅读&#xff0c;作者写的东西摘自论文&#xff0c;在此感谢xiaowei等的贡献 DoG尺度空间构造&#xff08;Scale-space extrema detection&#xff09;http://blog.csdn.net/xiaowei_cqu/article/details/8067881关键点搜索与定位&#xff08;Keypoint l…

遥感空间尺度转换技术(升尺度和降尺度)

遥感图像的一个基本特征是空间分辨率。目前已经可以有效获取大量不同空间分辨率遥感数据。 尺度和尺度转换已经成为遥感的核心问题之一,人们已经从不同角度提出了这一问题。尺度转换分为两种: 升尺度:从高分辨率到低分辨率的转换;降尺度:从低分辨率到高分辨率的转换。文章…

SIFT 尺度空间

最近也注意一些图像拼接方面的文章&#xff0c;很多很多&#xff0c;尤其是全景图拼接的&#xff0c;实际上类似佳能相机附加的软件&#xff0c;好多具备全景图拼接&#xff0c;多幅图像自动软件实现拼接&#xff0c;构成&#xff08;合成&#xff09;一幅全景图像&#xff08;…