java ThreadLocal(应用场景及使用方式及原理)
<h1>
<span class="link_title"><a href="/coslay/article/details/38293689">
java ThreadLocal(应用场景及使用方式及原理)
</a></span>
</h1>
<div class="article_manage clearfix">
<div class="article_r">
<span class="link_postdate">2014-07-30 10:15</span>
<span class="link_view" title="阅读次数">1240人阅读</span>
<span class="link_comments" title="评论次数"> <a href="#comments" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_pinglun'])">评论</a>(0)</span>
<span class="link_collect tracking-ad" data-mod="popu_171"> <a href="javascript:void(0);" onclick="javascript:collectArticle('java+ThreadLocal(%e5%ba%94%e7%94%a8%e5%9c%ba%e6%99%af%e5%8f%8a%e4%bd%bf%e7%94%a8%e6%96%b9%e5%bc%8f%e5%8f%8a%e5%8e%9f%e7%90%86)','38293689');return false;" title="收藏">收藏</a></span>
<span class="link_report"> <a href="#report" onclick="javascript:report(38293689,2);return false;" title="举报">举报</a></span>
</div>
</div>
<div class="embody" style="display:none" id="embody">
<span class="embody_t">本文章已收录于:</span>
<div class="embody_c" id="lib" value="{"err":0,"msg":"ok","data":[]}"></div>
</div>
<style type="text/css">
.embody{
padding:10px 10px 10px;
margin:0 -20px;
border-bottom:solid 1px #ededed;
}
.embody_b{
margin:0 ;
padding:10px 0;
}
.embody .embody_t,.embody .embody_c{
display: inline-block;
margin-right:10px;
}
.embody_t{
font-size: 12px;
color:#999;
}
.embody_c{
font-size: 12px;
}
.embody_c img,.embody_c em{
display: inline-block;
vertical-align: middle;
}
.embody_c img{
width:30px;
height:30px;
}
.embody_c em{
margin: 0 20px 0 10px;
color:#333;
font-style: normal;
}
</style>
<script type="text/javascript">
$(function () {
try
{
var lib = eval("("+$("#lib").attr("value")+")");
var html = "";
if (lib.err == 0) {
$.each(lib.data, function (i) {
var obj = lib.data[i];
//html += '<img src="' + obj.logo + '"/>' + obj.name + " ";
html += ' <a href="' + obj.url + '" target="_blank">';
html += ' <img src="' + obj.logo + '">';
html += ' <em><b>' + obj.name + '</b></em>';
html += ' </a>';
});
if (html != "") {
setTimeout(function () {
$("#lib").html(html);
$("#embody").show();
}, 100);
}
}
} catch (err)
{ }
});
</script>
<div class="category clearfix">
<div class="category_l">
<img src="http://static.blog.csdn.net/images/category_icon.jpg">
<span>分类:</span>
</div>
<div class="category_r">
<label onclick="GetCategoryArticles('2406917','qilixiang012','top','38293689');">
<span onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_fenlei']);">java 集合<em>(49)</em></span>
<img class="arrow-down" src="http://static.blog.csdn.net/images/arrow_triangle _down.jpg" style="display:inline;">
<img class="arrow-up" src="http://static.blog.csdn.net/images/arrow_triangle_up.jpg" style="display:none;">
<div class="subItem">
<div class="subItem_t"><a href="http://blog.csdn.net/qilixiang012/article/category/2406917" target="_blank">作者同类文章</a><i class="J_close">X</i></div>
<ul class="subItem_l" id="top_2406917">
</ul>
</div>
</label>
<label onclick="GetCategoryArticles('2857487','qilixiang012','top','38293689');">
<span onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_fenlei']);">java 并发<em>(135)</em></span>
<img class="arrow-down" src="http://static.blog.csdn.net/images/arrow_triangle _down.jpg" style="display:inline;">
<img class="arrow-up" src="http://static.blog.csdn.net/images/arrow_triangle_up.jpg" style="display:none;">
<div class="subItem">
<div class="subItem_t"><a href="http://blog.csdn.net/qilixiang012/article/category/2857487" target="_blank">作者同类文章</a><i class="J_close">X</i></div>
<ul class="subItem_l" id="top_2857487">
</ul>
</div>
</label>
</div>
</div>
<script type="text/javascript" src="http://static.blog.csdn.net/scripts/category.js"></script>
虽然ThreadLocal与并发问题相关,但是许多程序员仅仅将它作为一种用于“方便传参”的工具,胖哥认为这也许并不是ThreadLocal设计的目的,它本身是为线程安全和某些特定场景的问题而设计的。
ThreadLocal是什么呢!
每个ThreadLocal可以放一个线程级别的变量,但是它本身可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全。
例如:
RESOURCE代表一个可以存放String类型的ThreadLocal对象,此时任何一个线程可以并发访问这个变量,对它进行写入、读取操作,都是线程安全的。比如一个线程通过RESOURCE.set(“aaaa”);将数据写入ThreadLocal中,在任何一个地方,都可以通过RESOURCE.get();将值获取出来。
但是它也并不完美,有许多缺陷,就像大家依赖于它来做参数传递一样,接下来我们就来分析它的一些不好的地方。
为什么有些时候会将ThreadLocal作为方便传递参数的方式呢?例如当许多方法相互调用时,最初的设计可能没有想太多,有多少个参数就传递多少个变量,那么整个参数传递的过程就是零散的。进一步思考:若A方法调用B方法传递了8个参数,B方法接下来调用C方法->D方法->E方法->F方法等只需要5个参数,此时在设计API时就涉及5个参数的入口,这些方法在业务发展的过程中被许多地方所复用。
某一天,我们发现F方法需要加一个参数,这个参数在A方法的入口参数中有,此时,如果要改中间方法牵涉面会很大,而且不知道修改后会不会有Bug。作为程序员的我们可能会随性一想,ThreadLocal反正是全局的,就放这里吧,确实好解决。
但是此时你会发现系统中这种方式有点像在贴补丁,越贴越多,我们必须要求调用相关的代码都使用ThreadLocal传递这个参数,有可能会搞得乱七八糟的。换句话说,并不是不让用,而是我们要明确它的入口和出口是可控的。
诡异的ThreadLocal最难琢磨的是“作用域”,尤其是在代码设计之初很乱的情况下,如果再增加许多ThreadLocal,系统就会逐渐变成神龙见首不见尾的情况。有了这样一个省事的东西,可能许多小伙伴更加不在意设计,因为大家都认为这些问题都可以通过变化的手段来解决。胖哥认为这是一种恶性循环。
对于这类业务场景,应当提前有所准备,需要粗粒度化业务模型,即使要用ThreadLocal,也不是加一个参数就加一个ThreadLocal变量。例如,我们可以设计几种对象来封装入口参数,在接口设计时入口参数都以对象为基础。
也许一个类无法表达所有的参数意思,而且那样容易导致强耦合。
通常我们按照业务模型分解为几大类型对象作为它们的参数包装,并且将按照对象属性共享情况进行抽象,在继承关系的每一个层次各自扩展相应的参数,或者说加参数就在对象中加,共享参数就在父类中定义,这样的参数就逐步规范化了。
我们回到正题,探讨一下ThreadLocal到底是用来做什么的?为此我们探讨下文中的几个话题。
(1)应用场景及使用方式
为了说明ThreadLocal的应用场景,我们来看一个框架的例子。Spring的事务管理器通过AOP切入业务代码,在进入业务代码前,会根据对应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。并且Spring也将DataSource进行了包装,重写了其中的getConnection()方法,或者说该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。为什么要放在ThreadLocal里面呢?因为Spring在AOP后并不能向应用程序传递参数,应用程序的每个业务代码是事先定义好的,Spring并不会要求在业务代码的入口参数中必须编写Connection的入口参数。此时Spring选择了ThreadLocal,通过它保证连接对象始终在线程内部,任何时候都能拿到,此时Spring非常清楚什么时候回收这个连接,也就是非常清楚什么时候从ThreadLocal中删除这个元素(在9.2节中会详细讲解)。
从Spring事务管理器的设计上可以看出,Spring利用ThreadLocal得到了一个很完美的设计思路,同时它在设计时也十分清楚ThreadLocal中元素应该在什么时候删除。由此,我们简单地认为ThreadLocal尽量使用在一个全局的设计上,而不是一种打补丁的间接方法。
了解了基本应用场景后,接下来看一个例子。定义一个类用于存放静态的ThreadLocal对象,通过多个线程并行地对ThreadLocal对象进行set、get操作,并将值进行打印,来看看每个线程自己设置进去的值和取出来的值是否是一样的。代码如下:
代码清单5-8 简单的ThreadLocal例子
- public class ThreadLocalTest {
- static class ResourceClass {
- public final static ThreadLocal<String> RESOURCE_1 =
- new ThreadLocal<String>();
- public final static ThreadLocal<String> RESOURCE_2 =
- new ThreadLocal<String>();
- }
- static class A {
- public void setOne(String value) {
- ResourceClass.RESOURCE_1.set(value);
- }
- public void setTwo(String value) {
- ResourceClass.RESOURCE_2.set(value);
- }
- }
- static class B {
- public void display() {
- System.out.println(ResourceClass.RESOURCE_1.get()
- + ":" + ResourceClass.RESOURCE_2.get());
- }
- }
- public static void main(String []args) {
- final A a = new A();
- final B b = new B();
- for(int i = 0 ; i < 15 ; i ++) {
- final String resouce1 = "线程-" + I;
- final String resouce2 = " value = (" + i + ")";
- new Thread() {
- public void run() {
- try {
- a.setOne(resouce1);
- a.setTwo(resouce2);
- b.display();
- }finally {
- ResourceClass.RESOURCE_1.remove();
- ResourceClass.RESOURCE_2.remove();
- }
- }
- }.start();
- }
- }
- }
关于这段代码,我们先说几点。
◎ 定义了两个ThreadLocal变量,最终的目的就是要看最后两个值是否能对应上,这样才有机会证明ThreadLocal所保存的数据可能是线程私有的。
◎ 使用两个内部类只是为了使测试简单,方便大家直观理解,大家也可以将这个例子的代码拆分到多个类中,得到的结果是相同的。
◎ 测试代码更像是为了方便传递参数,因为它确实传递参数很方便,但这仅仅是为了测试。
◎ 在finally里面有remove()操作,是为了清空数据而使用的。为何要清空数据,在后文中会继续介绍细节。
测试结果如下:
线程-6: value = (6)
线程-9: value = (9)
线程-0: value = (0)
线程-10: value = (10)
线程-12: value = (12)
线程-14: value = (14)
线程-11: value = (11)
线程-3: value = (3)
线程-5: value = (5)
线程-13: value = (13)
线程-2: value = (2)
线程-4: value = (4)
线程-8: value = (8)
线程-7: value = (7)
线程-1: value = (1)
大家可以看到输出的线程顺序并非最初定义线程的顺序,理论上可以说明多线程应当是并发执行的,但是依然可以保持每个线程里面的值是对应的,说明这些值已经达到了线程私有的目的。
不是说共享变量无法做到线程私有吗?它又是如何做到线程私有的呢?这就需要我们知道一点点原理上的东西,否则用起来也没那么放心,请看下面的介绍。
(2)ThreadLocal内在原理
从前面的操作可以发现,ThreadLocal最常见的操作就是set、get、remove三个动作,下面来看看这三个动作到底做了什么事情。首先看set操作,源码片段如图5-5所示。图5-5 ThreadLcoal.set源码片段
图5-5中的第一条代码取出了当前线程t,然后调用getMap(t)方法时传入了当前线程,换句话说,该方法返回的ThreadLocalMap和当前线程有点关系,我们先记录下来。进一步判定如果这个map不为空,那么设置到Map中的Key就是this,值就是外部传入的参数。这个this是什么呢?就是定义的ThreadLocal对象。
代码中有两条路径需要追踪,分别是getMap(Thread)和createMap(Thread , T)。首先来看看getMap(t)操作,如图5-6所示。
图5-6 getMap(Thread)操作
在这里,我们看到ThreadLocalMap其实就是线程里面的一个属性,它在Thread类中的定义是:
ThreadLocal.ThreadLocalMap threadLocals = null;
这种方法很容易让人混淆,因为这个ThreadLocalMap是ThreadLocal里面的内部类,放在了Thread类里面作为一个属性而存在,ThreadLocal本身成为这个Map里面存放的Key,用户输入的值是Value。太乱了,理不清楚了,画个图来看看(见图5-7)。
简单来讲,就是这个Map对象在Thread里面作为私有的变量而存在,所以是线程安全的。ThreadLocal通过Thread.currentThread()获取当前的线程就能得到这个Map对象,同时将自身作为Key发起写入和读取,由于将自身作为Key,所以一个ThreadLocal对象就能存放一个线程中对应的Java对象,通过get也自然能找到这个对象。
图5-7 Thread与ThreadLocal的伪代码关联关系
如果还没有理解,则可以将思维放宽一点。当定义变量String a时,这个“a”其实只是一个名称(在第3章中已经说到了常量池),虚拟机需要通过符号表来找到相应的信息,而这种方式正好就像一种K-V结构,底层的处理方式也确实很接近这样,这里的处理方式是显式地使用Map来存放数据,这也是一种实现手段的变通。
现在有了思路,继续回到上面的话题,为了验证前面的推断和理解,来看看createMap方法的细节,如图5-8所示。
图5-8 createMap操作
这段代码是执行一个创建新的Map的操作,并且将第一个值作为这个Map的初始化值,由于这个Map是线程私有的,不可能有另一个线程同时也在对它做put操作,因此这里的赋值和初始化是绝对线程安全的,也同时保证了每一个外部写入的值都将写入到Map对象中。
最后来看看get()、remove()代码,或许看到这里就可以认定我们的理论是正确的,如图5-9所示。
图5-9 get()/remove()方法的代码片段
给我们的感觉是,这样实现是一种技巧,而不是一种技术。
其实是技巧还是技术完全是从某种角度来看的,或者说是从某种抽象层次来看的,如果这段代码在C++中实现,难道就叫技术,不是技巧了吗?当然不是!胖哥认为技术依然是建立在思想和方法基础上的,只是看实现的抽象层次在什么级别。就像在本书中多个地方探讨的一些基础原理一样,我们探讨了它的思想,其实它的实现也是基于某种技巧和手段的,只是对程序封装后就变成了某种语法和API,因此胖哥认为,一旦学会使用技巧思考问题,就学会了通过技巧去看待技术本身。我们应当通过这种设计,学会一种变通和发散的思维,学会理解各种各样的场景,这样便可以积累许多真正的财富,这些财富不是通过某些工具的使用或测试就可以获得的。
ThreadLocal的这种设计很完美吗?
不是很完美,它依然有许多坑,在这里对它容易误导程序员当成传参工具就不再多提了,下面我们来看看它的使用不当会导致什么技术上的问题。
(3)ThreadLocal的坑
通过上面的分析,我们可以认识到ThreadLocal其实是与线程绑定的一个变量,如此就会出现一个问题:如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。因此,ThreadLocal的一个很大的“坑”就是当使用不当时,导致使用者不知道它的作用域范围。大家可能认为线程结束后ThreadLocal应该就回收了,如果线程真的注销了确实是这样的,但是事实有可能并非如此,例如在线程池中对线程管理都是采用线程复用的方法(Web容器通常也会采用线程池),在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。那么相应的ThreadLocal变量的生命周期也将不可预测。
也许系统中定义少量几个ThreadLocal变量也无所谓,因为每次set数据时是用ThreadLocal本身作为Key的,相同的Key肯定会替换原来的数据,原来的数据就可以被释放了,理论上不会导致什么问题。但世事无绝对,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会开始膨胀。
抛开代码本身的问题,举一个极端的例子。如果不想定义太多的ThreadLocal变量,就用一个HashMap来存放,这貌似没什么问题。由于ThreadLocal在程序的任何一个地方都可以用得到,在某些设计不当的代码中很难知道这个HashMap写入的源头,在代码中为了保险起见,通常会先检查这个HashMap是否存在,若不存在,则创建一个HashMap写进去;若存在,通常也不会替换掉,因为代码编写者通常会“害怕”因为这种替换会丢掉一些来自“其他地方写入HashMap的数据”,从而导致许多不可预见的问题。
在这样的情况下,HashMap第一次放入ThreadLocal中也许就一直不会被释放,而这个HashMap中可能开始存放许多Key-Value信息,如果业务上存放的Key值在不断变化(例如,将业务的ID作为Key),那么这个HashMap就开始不断变长,并且很可能在每个线程中都有一个这样的HashMap,逐渐地形成了间接的内存泄漏。曾经有很多人吃过这个亏,而且吃亏的时候发现这样的代码可能不是在自己的业务系统中,而是出现在某些二方包、三方包中(开源并不保证没有问题)。
要处理这种问题很复杂,不过首先要保证自己编写的代码是没问题的,要保证没问题不是说我们不去用ThreadLocal,甚至不去学习它,因为它肯定有其应用价值。在使用时要明白ThreadLocal最难以捉摸的是“不知道哪里是源头”(通常是代码设计不当导致的),只有知道了源头才能控制结束的部分,或者说我们从设计的角度要让ThreadLocal的set、remove有始有终,通常在外部调用的代码中使用finally来remove数据,只要我们仔细思考和抽象是可以达到这个目的的。有些是二方包、三方包的问题,对于这些问题我们需要学会的是找到问题的根源后解决,关于二方包、三方包的运行跟踪,可参看第3.7.9节介绍的BTrace工具。
补充:在任何异步程序中(包括异步I/O、非阻塞I/O),ThreadLocal的参数传递是不靠谱的,因为线程将请求发送后,就不再等待远程返回结果继续向下执行了,真正的返回结果得到后,处理的线程可能是另一个。
#####################################个人总结 ####################################
Thread.java源码中:
即:每个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(如下所示),所以Thread类可以进行引用.- static class ThreadLocalMap {
当在ThreadLocal中进行设值的时候:
- 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(ThreadLocal firstKey, Object firstValue) {
- table = new Entry[INITIAL_CAPACITY];
- int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
- table[i] = new Entry(firstKey, firstValue);
- size = 1;
- setThreshold(INITIAL_CAPACITY);
- }
总结:
深入研究java.lang.ThreadLocal类:http://blog.csdn.net/xiaohulunb/article/details/19603611
API说明:
ThreadLocal(),T get(),protected T initialValue(),void remove(),void set(T value)
典型实例:
1.Hiberante的Session 工具类HibernateUtil
2.通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。
ThreadLocal使用的一般步骤:
- 1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
- 2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
- 3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。
与Synchonized的对比:
- ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
- Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
一句话理解ThreadLocal:向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了。
使用ThreadLocal改进你的层次的划分(spring事务的实现):http://blog.csdn.net/zhouyong0/article/details/7761835
源码剖析之ThreadLocal:http://wangxinchun.iteye.com/blog/1884228
Java中的ThreadLocal源码解析(上):http://maosidiaoxian.iteye.com/blog/1939142
ThreadLocal与synchronized:http://blog.csdn.net/yangairong1984/article/details/2294572
Java线程:深入ThreadLocal:http://lavasoft.blog.51cto.com/62575/258459(一个ThreadLocal的模拟实现)
Java多线程(六)、ThreadLocal类:http://blog.csdn.net/lonelyroamer/article/details/7998137
<div id="digg" articleid="38293689">
<dl id="btnDigg" class="digg digg_disable" onclick="btndigga();">
<dt>顶</dt>
<dd>0</dd>
</dl>
<dl id="btnBury" class="digg digg_disable" onclick="btnburya();">
<dt>踩</dt>
<dd>1</dd>
</dl>
</div>
<div class="tracking-ad" data-mod="popu_222"><a href="javascript:void(0);"> </a> </div>
<div class="tracking-ad" data-mod="popu_223"> <a href="javascript:void(0);"> </a></div>
<script type="text/javascript">
function btndigga() {
$(".tracking-ad[data-mod='popu_222'] a").click();
}
function btnburya() {
$(".tracking-ad[data-mod='popu_223'] a").click();
}
</script>
<div style="clear:both; height:10px;"></div>
<div class="similar_article" style="">
<h4>我的同类文章</h4>
<div class="similar_c" style="margin:20px 0px 0px 0px">
<div class="similar_c_t">
<label class="similar_cur">
<span style="cursor:pointer" onclick="GetCategoryArticles('2406917','qilixiang012','foot','38293689');">java 集合<em>(49)</em></span>
</label>
<label class="">
<span style="cursor:pointer" onclick="GetCategoryArticles('2857487','qilixiang012','foot','38293689');">java 并发<em>(135)</em></span>
</label>
</div>
<div class="similar_wrap tracking-ad" data-mod="popu_141" style="max-height:195px;">
<a href="http://blog.csdn.net" style="display:none">http://blog.csdn.net</a>
<ul class="similar_list fl"><li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/46894821" id="foot_aritcle_46894821undefined9730249543451324" target="_blank" title="java 小议Iterator">java 小议Iterator</a><span>2015-07-15</span><label><i>阅读</i><b>257</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/46862273" id="foot_aritcle_46862273undefined43856347445337973" target="_blank" title="java ArrayList循环遍历并删除元素的常见陷阱">java ArrayList循环遍历并删除元素的常见陷阱</a><span>2015-07-13</span><label><i>阅读</i><b>498</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/45288567" id="foot_aritcle_45288567undefined03673864889357459" target="_blank" title="java LinkedTransferQueue">java LinkedTransferQueue</a><span>2015-04-26</span><label><i>阅读</i><b>352</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/45268931" id="foot_aritcle_45268931undefined81938044079141" target="_blank" title="java DelayQueue">java DelayQueue</a><span>2015-04-25</span><label><i>阅读</i><b>284</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/44890831" id="foot_aritcle_44890831undefined2781548834068488" target="_blank" title="java 集合Set架构">java 集合Set架构</a><span>2015-04-05</span><label><i>阅读</i><b>316</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/44890693" id="foot_aritcle_44890693undefined12093963845138322" target="_blank" title="java 集合Collection架构">java 集合Collection架构</a><span>2015-04-05</span><label><i>阅读</i><b>298</b></label></li> </ul>
<ul class="similar_list fr"><li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/46893801" id="foot_aritcle_46893801undefined35230271620561715" target="_blank" title="java HashMap两种遍历方式的深入研究">java HashMap两种遍历方式的深入研究</a><span>2015-07-15</span><label><i>阅读</i><b>266</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/46504041" id="foot_aritcle_46504041undefined4243491432023332" target="_blank" title="java 慎用ArrayList的contains方法,使用HashSet的contains方法代替">java 慎用ArrayList的contains方法,使用HashSet的contains方法代替</a><span>2015-06-15</span><label><i>阅读</i><b>323</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/45273285" id="foot_aritcle_45273285undefined9861883598248877" target="_blank" title="java SynchronousQueue">java SynchronousQueue</a><span>2015-04-25</span><label><i>阅读</i><b>348</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/44891035" id="foot_aritcle_44891035undefined39639768662125374" target="_blank" title="Java 集合fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)">Java 集合fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)</a><span>2015-04-05</span><label><i>阅读</i><b>350</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/qilixiang012/article/details/44890761" id="foot_aritcle_44890761undefined5594078665927797" target="_blank" title="java 集合Map架构">java 集合Map架构</a><span>2015-04-05</span><label><i>阅读</i><b>284</b></label></li> </ul>
<a href="http://blog.csdn.net/qilixiang012/article/category/2406917" class="MoreArticle">更多文章</a></div>
</div>
</div>
<script type="text/javascript">
$(function () {
GetCategoryArticles('2406917', 'qilixiang012','foot','38293689');
});
</script>
<div>
<ins data-revive-zoneid="206" data-revive-id="8c38e720de1c90a6f6ff52f3f89c4d57"></ins>
</div>
<dt><span>猜你在找</span></dt>
<div id="adCollege" style="width: 42%;float: left;">
<script src="http://csdnimg.cn/jobreco/job_reco.js" type="text/javascript"></script>
<script type="text/javascript">
csdn.position.showEdu({
sourceType: "blog",
searchType: "detail",
searchKey: "38293689",
username: "",
recordcount: "5",
containerId: "adCollege" //容器DIV的id。
});
</script>
<div class="tracking-ad" data-mod="popu_84"><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px; white-space: nowrap;"><a href="http://edu.csdn.net/course/detail/3837" title="WEB安全攻防技术精讲视频教程(全漏洞原理+攻击手段+测试方法+预防措施)" strategy="v4:content" target="_blank">WEB安全攻防技术精讲视频教程(全漏洞原理+攻击手段+测试方法+预防措施)</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px; white-space: nowrap;"><a href="http://edu.csdn.net/course/detail/4034" title="2017软考信息安全工程师应用技术教程" strategy="v4:content" target="_blank">2017软考信息安全工程师应用技术教程</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px; white-space: nowrap;"><a href="http://edu.csdn.net/course/detail/2591" title="Redis(最常用的NoSQL数据库技术,互联网行业Java工程师必备)" strategy="v4:content" target="_blank">Redis(最常用的NoSQL数据库技术,互联网行业Java工程师必备)</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px; white-space: nowrap;"><a href="http://edu.csdn.net/course/detail/3577" title="java核心技术精讲" strategy="v4:content" target="_blank">java核心技术精讲</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px; white-space: nowrap;"><a href="http://edu.csdn.net/course/detail/852" title="国内第1套_Spring4 视频教程" strategy="v4:content" target="_blank">国内第1套_Spring4 视频教程</a></dd></div></div>
<div id="res" data-mod="popu_36" class="tracking-ad" style="width: 42%; float: left; margin-right: 30px; display: block;"><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px;"><a href="http://blog.csdn.net/he90227/article/details/52485484" title="阿里面试题不知真假" strategy="SearchAlgorithm">阿里面试题不知真假</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px;"><a href="http://blog.csdn.net/duheaven/article/details/16803785" title="JVM体系结构" strategy="SearchAlgorithm">JVM体系结构</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px;"><a href="http://blog.csdn.net/sonny543/article/details/51336457" title="threadlocal原理及常用应用场景" strategy="SearchAlgorithm">threadlocal原理及常用应用场景</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px;"><a href="http://blog.csdn.net/z69183787/article/details/51490100" title="ThreadLocal 内部实现和应用场景" strategy="SearchAlgorithm">ThreadLocal 内部实现和应用场景</a></dd><dd style="background:url(http://static.blog.csdn.net/skin/default/images/blog-dot-red3.gif) no-repeat 0 10px;"><a href="http://blog.csdn.net/a13272899370/article/details/6611554" title="设计模式之-ThreadLocal设计模式应用场景" strategy="SearchAlgorithm">设计模式之-ThreadLocal设计模式应用场景</a></dd></div>
<div id="ad_cen">
<ins data-revive-zoneid="199" data-revive-id="8c38e720de1c90a6f6ff52f3f89c4d57"></ins>
</div>
<!-- 广告位开始 -->
<ins data-revive-zoneid="72" data-revive-id="8c38e720de1c90a6f6ff52f3f89c4d57"></ins>
<!-- 广告位结束 -->
<div id="ad_bot">
</div>
<a id="quick-reply" class="btn btn-top q-reply" title="快速回复" style="display:none;">
<img src="http://static.blog.csdn.net/images/blog-icon-reply.png" alt="快速回复">
</a>
<a id="d-top-a" class="btn btn-top backtop" style="display: none;" title="返回顶部" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_huidaodingbu'])">
<img src="http://static.blog.csdn.net/images/top.png" alt="TOP">
</a>
<h5>
<a href="http://www.csdn.net/tag/" target="_blank">核心技术类目</a></h5>
<div class="classify">
全部主题
Hadoop
AWS
移动游戏
Java
Android
iOS
Swift
智能硬件
Docker
OpenStack
VPN
Spark
ERP
IE10
Eclipse
CRM
JavaScript
数据库
Ubuntu
NFC
WAP
jQuery
BI
HTML5
Spring
Apache
.NET
API
HTML
SDK
IIS
Fedora
XML
LBS
Unity
Splashtop
UML
components
Windows Mobile
Rails
QEMU
KDE
Cassandra
CloudStack
FTC
coremail
OPhone
CouchBase
云计算
iOS6
Rackspace
Web App
SpringSide
Maemo
Compuware
大数据
aptech
Perl
Tornado
Ruby
Hibernate
ThinkPHP
HBase
Pure
Solr
Angular
Cloud Foundry
Redis
Scala
Django
Bootstrap
<div class="clear">
</div>
</div>
暂无评论