首页 > 技术文章 > java ThreadLocal(应用场景及使用方式及原理)

jobs-lgy 2017-02-18 14:38 原文

<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="{&quot;err&quot;:0,&quot;msg&quot;:&quot;ok&quot;,&quot;data&quot;:[]}"></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 + "&nbsp;&nbsp;";
                    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可以放一个线程级别的变量,但是它本身可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全。
例如:

  1. public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();  
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例子

  1. public class ThreadLocalTest {  
  2.   
  3.     static class ResourceClass {  
  4.   
  5.         public final static ThreadLocal<String> RESOURCE_1 =  
  6.                                        new ThreadLocal<String>();  
  7.   
  8.         public final static ThreadLocal<String> RESOURCE_2 =  
  9.                                        new ThreadLocal<String>();  
  10.   
  11.     }  
  12.   
  13.     static class A {  
  14.   
  15.         public void setOne(String value) {  
  16.             ResourceClass.RESOURCE_1.set(value);  
  17.         }  
  18.   
  19.         public void setTwo(String value) {  
  20.             ResourceClass.RESOURCE_2.set(value);  
  21.         }  
  22.     }  
  23.   
  24.     static class B {  
  25.         public void display() {  
  26.             System.out.println(ResourceClass.RESOURCE_1.get()  
  27.                         + ":" + ResourceClass.RESOURCE_2.get());  
  28.         }  
  29.     }  
  30.   
  31.     public static void main(String []args) {  
  32.         final A a = new A();  
  33.         final B b = new B();  
  34.         for(int i = 0 ; i < 15 ; i ++) {  
  35.             final String resouce1 = "线程-" + I;  
  36.             final String resouce2 = " value = (" + i + ")";  
  37.             new Thread() {  
  38.                 public void run() {  
  39.                 try {  
  40.                     a.setOne(resouce1);  
  41.                     a.setTwo(resouce2);  
  42.                     b.display();  
  43.                 }finally {  
  44.                     ResourceClass.RESOURCE_1.remove();  
  45.                     ResourceClass.RESOURCE_2.remove();  
  46.                 }  
  47.             }  
  48.         }.start();  
  49.         }  
  50.     }  
  51. }  

关于这段代码,我们先说几点。
◎ 定义了两个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源码中:

  1. ThreadLocal.ThreadLocalMap threadLocals = null;  
即:每个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(如下所示),所以Thread类可以进行引用.

  1. static class ThreadLocalMap {  
所以每个线程都会有一个ThreadLocal.ThreadLocalMap对象的引用

当在ThreadLocal中进行设值的时候:

  1. public void set(T value) {  
  2.     Thread t = Thread.currentThread();  
  3.     ThreadLocalMap map = getMap(t);  
  4.     if (map != null)  
  5.         map.set(this, value);  
  6.     else  
  7.         createMap(t, value);  
  8. }  

  1. ThreadLocalMap getMap(Thread t) {  
  2.     return t.threadLocals;  
  3. }  

首先获取当前线程的引用,然后获取当前线程的ThreadLocal.ThreadLocalMap对象(t.threadLocals变量就是ThreadLocal.ThreadLocalMap的变量),如果该对象为空就创建一个,如下所示:

  1. void createMap(Thread t, T firstValue) {  
  2.     t.threadLocals = new ThreadLocalMap(this, firstValue);  
  3. }  
这个this变量就是ThreadLocal的引用,对于同一个ThreadLocal对象每个线程都是相同的,但是每个线程各自有一个ThreadLocal.ThreadLocalMap对象保存着各自ThreadLocal引用为key的值,所以互不影响,而且:如果你新建一个ThreadLocal的对象,这个对象还是保存在每个线程同一个ThreadLocal.ThreadLocalMap对象之中,因为一个线程只有一个ThreadLocal.ThreadLocalMap对象,这个对象是在第一个ThreadLocal第一次设值的时候进行创建,如上所述的createMap方法.

  1. ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {  
  2.     table = new Entry[INITIAL_CAPACITY];  
  3.     int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  
  4.     table[i] = new Entry(firstKey, firstValue);  
  5.     size = 1;  
  6.     setThreshold(INITIAL_CAPACITY);  
  7. }  



总结:

深入研究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. 1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。  
  2. 2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。  
  3. 3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。  

与Synchonized的对比:

  1. ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。  
  2.    
  3. 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);">&nbsp;</a>   </div>
<div class="tracking-ad" data-mod="popu_223"> <a href="javascript:void(0);">&nbsp;</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>

参考知识库

img

.NET知识库

img

Java SE知识库

img

Java EE知识库

img

Java 知识库

img

软件测试知识库

 <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>
<!-- 广告位结束 -->
查看评论

  暂无评论

* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
<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>

                    <div class="clear">
                    </div>
                </div>

推荐阅读