ThreadLocal源码分析

参考:

http://www.cnblogs.com/dolphin0520/p/3920407.html

https://www.cnblogs.com/coshaho/p/5127135.html

https://www.jianshu.com/p/ee8c9dccc953

想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理。首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两个应用场景。

  以下是本文目录大纲:

  一.对ThreadLocal的理解

  二.深入解析ThreadLocal类

  三.ThreadLocal的应用场景

  若有不正之处请多多谅解,并欢迎批评指正。

  请尊重作者劳动成果,转载请标明原文链接:

  http://www.cnblogs.com/dolphin0520/p/3920407.html

一.对ThreadLocal的理解

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

  这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。

  我们还是先来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ConnectionManager {
     
    private static Connection connect = null;
     
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

  假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。

  所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。

  这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。

  那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。

  到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ConnectionManager {
     
    private  Connection connect = null;
     
    public Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}
 
 
class Dao{
    public void insert() {
        ConnectionManager connectionManager = new ConnectionManager();
        Connection connection = connectionManager.openConnection();
         
        //使用connection进行操作
         
        connectionManager.closeConnection();
    }
}

  这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。

  那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

  但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

二.深入解析ThreadLocal类

  在上面谈到了对ThreadLocal的一些理解,那我们下面来看一下具体ThreadLocal是如何实现的。

  先了解一下ThreadLocal类提供的几个方法:

1
2
3
4
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

  get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。

  首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。

  先看下get方法的实现:

  

  第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。

  如果获取成功,则返回value值。

  如果map为空,则调用setInitialValue方法返回value。

  我们上面的每一句来仔细分析:

  首先看一下getMap方法中做了什么:

  

  可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

  那么我们继续取Thread类中取看一下成员变量threadLocals是什么:

  

  实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:

  

  可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

  然后再继续看setInitialValue方法的具体实现:

  很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:

  

  至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:

  首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

  初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

  然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

  下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
         
         
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

  这段代码的输出结果为:

  

  从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。

  总结一下:

  1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

  2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

  3)在进行get之前,必须先set,否则会报空指针异常;

      如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

    因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

  

  看下面这个例子:

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
 
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

  在main线程中,没有先set,直接get的话,运行时会报空指针异常。

  但是如果改成下面这段代码,即重写了initialValue方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){
        protected Long initialValue() {
            return Thread.currentThread().getId();
        };
    };
    ThreadLocal<String> stringLocal = new ThreadLocal<String>(){;
        protected String initialValue() {
            return Thread.currentThread().getName();
        };
    };
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
 
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

  就可以直接不用先set而直接调用get了。

三.ThreadLocal的应用场景

  最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

  如:

1
2
3
4
5
6
7
8
9
10
private static ThreadLocal<Connection> connectionHolder
new ThreadLocal<Connection>() {
public Connection initialValue() {
    return DriverManager.getConnection(DB_URL);
}
};
 
public static Connection getConnection() {
return connectionHolder.get();
}

  下面这段代码摘自:

  http://www.iteye.com/topic/103804

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final ThreadLocal threadSession = new ThreadLocal();
 
public static Session getSession() throws InfrastructureException {
    Session s = (Session) threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
}

  参考资料:

  《深入理解Java虚拟机》

  《Java编程思想》

  http://ifeve.com/thread-management-10/

  http://www.ibm.com/developerworks/cn/java/j-threads/index3.html

  http://www.iteye.com/topic/103804

  http://www.iteye.com/topic/777716

  http://www.iteye.com/topic/757478

  http://blog.csdn.net/ghsau/article/details/15732053

  http://ispring.iteye.com/blog/162982

  http://blog.csdn.net/imzoer/article/details/8262101

  http://www.blogjava.net/wumi9527/archive/2010/09/10/331654.html

  http://bbs.csdn.net/topics/380049261

一、用法

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package com.coshaho.reflect;
 
/**
 * ThreadLocal用法
 * @author coshaho
 *
 */
public class MyThreadLocal
{
    private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
        /**
         * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
         */
        @Override
        protected Object initialValue()
        {
            System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
            return null;
        }
    };
     
    public static void main(String[] args)
    {
        new Thread(new MyIntegerTask("IntegerTask1")).start();
        new Thread(new MyStringTask("StringTask1")).start();
        new Thread(new MyIntegerTask("IntegerTask2")).start();
        new Thread(new MyStringTask("StringTask2")).start();
    }
     
    public static class MyIntegerTask implements Runnable
    {
        private String name;
         
        MyIntegerTask(String name)
        {
            this.name = name;
        }
 
        @Override
        public void run()
        {
            for(int i = 0; i < 5; i++)
            {
                // ThreadLocal.get方法获取线程变量
                if(null == MyThreadLocal.threadLocal.get())
                {
                    // ThreadLocal.et方法设置线程变量
                    MyThreadLocal.threadLocal.set(0);
                    System.out.println("线程" + name + ": 0");
                }
                else
                {
                    int num = (Integer)MyThreadLocal.threadLocal.get();
                    MyThreadLocal.threadLocal.set(num + 1);
                    System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
                    if(i == 3)
                    {
                        MyThreadLocal.threadLocal.remove();
                    }
                }
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }  
        }
         
    }
     
    public static class MyStringTask implements Runnable
    {
        private String name;
         
        MyStringTask(String name)
        {
            this.name = name;
        }
 
        @Override
        public void run()
        {
            for(int i = 0; i < 5; i++)
            {
                if(null == MyThreadLocal.threadLocal.get())
                {
                    MyThreadLocal.threadLocal.set("a");
                    System.out.println("线程" + name + ": a");
                }
                else
                {
                    String str = (String)MyThreadLocal.threadLocal.get();
                    MyThreadLocal.threadLocal.set(str + "a");
                    System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
                }
                try
                {
                    Thread.sleep(800);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }
         
    }
<strong>}
</strong>

运行结果如下:

调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask1: 0
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask2: 0
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程StringTask1: a
线程StringTask2: a
线程StringTask1: aa
线程StringTask2: aa
线程IntegerTask1: 1
线程IntegerTask2: 1
线程StringTask1: aaa
线程StringTask2: aaa
线程IntegerTask2: 2
线程IntegerTask1: 2
线程StringTask2: aaaa
线程StringTask1: aaaa
线程IntegerTask2: 3
线程IntegerTask1: 3
线程StringTask1: aaaaa
线程StringTask2: aaaaa
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask2: 0
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程IntegerTask1: 0

二、原理

线程共享变量缓存如下:

Thread.ThreadLocalMap<ThreadLocalObject>;

1、Thread: 当前线程,可以通过Thread.currentThread()获取。

2、ThreadLocal:我们的static ThreadLocal变量。

3、Object: 当前线程共享变量。

我们调用ThreadLocal.get方法时,实际上是从当前线程中获取ThreadLocalMap<ThreadLocalObject>,然后根据当前ThreadLocal获取当前线程共享变量Object。

ThreadLocal.set,ThreadLocal.remove实际上是同样的道理。

这种存储结构的好处:

1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。

2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。

关于ThreadLocalMap<ThreadLocalObject>弱引用问题:

当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<nullObject>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。

虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。

1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;

2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。

源码分析参考:

http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/

ThreadLocal是什么、有什么、能做什么?

ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全。

ThreadLocal提供的方法

 
ThreadLocal API

对于ThreadLocal而言,常用的方法,就是get/set/initialValue方法。

我们先来看一个例子

 
demo

运行结果

 
是你想象中的结果么?

很显然,在这里,并没有通过ThreadLocal达到线程隔离的机制,可是ThreadLocal不是保证线程安全的么?这是什么鬼?

虽然,ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象呢?这个时候ThreadLocal就失效了。仔细观察下图中的代码,你会发现,threadLocal在初始化时返回的都是同一个对象a!

看一看ThreadLocal源码

我们直接看最常用的set操作:

 
set
 
线程局部变量
 
createMap

你会看到,set需要首先获得当前线程对象Thread;

然后取出当前线程对象的成员变量ThreadLocalMap;

如果ThreadLocalMap存在,那么进行KEY/VALUE设置,KEY就是ThreadLocal;

如果ThreadLocalMap没有,那么创建一个;

说白了,当前线程中存在一个Map变量,KEY是ThreadLocal,VALUE是你设置的值。

看一下get操作:

 
get

这里其实揭示了ThreadLocalMap里面的数据存储结构,从上面的代码来看,ThreadLocalMap中存放的就是Entry,Entry的KEY就是ThreadLocal,VALUE就是值。

ThreadLocalMap.Entry:

 
弱引用?

在JAVA里面,存在强引用、弱引用、软引用、虚引用。这里主要谈一下强引用和弱引用。

强引用,就不必说了,类似于:

A a = new A();

B b = new B();

考虑这样的情况:

C c = new C(b);

b = null;

考虑下GC的情况。要知道b被置为null,那么是否意味着一段时间后GC工作可以回收b所分配的内存空间呢?答案是否定的,因为即便b被置为null,但是c仍然持有对b的引用,而且还是强引用,所以GC不会回收b原先所分配的空间!既不能回收利用,又不能使用,这就造成了内存泄露

那么如何处理呢?

可以c = null;也可以使用弱引用!(WeakReference w = new WeakReference(b);)

分析到这里,我们可以得到:

 
内存结构图

这里我们思考一个问题:ThreadLocal使用到了弱引用,是否意味着不会存在内存泄露呢?

首先来说,如果把ThreadLocal置为null,那么意味着Heap中的ThreadLocal实例不在有强引用指向,只有弱引用存在,因此GC是可以回收这部分空间的,也就是key是可以回收的。但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。

因此,只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间内不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,比如使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。

那么如何有效的避免呢?

事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!

并发-ThreadLocal源码分析的更多相关文章

  1. 并发编程(四)—— ThreadLocal源码分析及内存泄露预防

    今天我们一起探讨下ThreadLocal的实现原理和源码分析.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两 ...

  2. Java并发编程之ThreadLocal源码分析

    ## 1 一句话概括ThreadLocal<font face="微软雅黑" size=4>  什么是ThreadLocal?顾名思义:线程本地变量,它为每个使用该对象 ...

  3. ThreadLocal源码分析-黄金分割数的使用

    前提 最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量.问题是解决了,但是后来发现对T ...

  4. Java多线程学习之ThreadLocal源码分析

    0.概述 ThreadLocal,即线程本地变量,是一个以ThreadLocal对象为键.任意对象为值的存储结构.它可以将变量绑定到特定的线程上,使每个线程都拥有改变量的一个拷贝,各线程相同变量间互不 ...

  5. 【JAVA】ThreadLocal源码分析

    ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...

  6. ThreadLocal详解,ThreadLocal源码分析,ThreadLocal图解

    本文脉路: 概念阐释 ---->  原理图解  ------> 源码分析 ------>  思路整理  ----> 其他补充. 一.概念阐述. ThreadLocal 是一个为 ...

  7. Java -- 基于JDK1.8的ThreadLocal源码分析

    1,最近在做一个需求的时候需要对外部暴露一个值得应用  ,一般来说直接写个单例,将这个成员变量的值暴露出去就ok了,但是当时突然灵机一动(现在回想是个多余的想法),想到handle源码里面有使用过Th ...

  8. 并发-AQS源码分析

    AQS源码分析 参考: http://www.cnblogs.com/waterystone/p/4920797.html https://blog.csdn.net/fjse51/article/d ...

  9. 并发-CopyOnWrite源码分析

    CopyOnWrite源码分析 参考: https://blog.csdn.net/linsongbin1/article/details/54581787 http://ifeve.com/java ...

随机推荐

  1. Flash Builder 相关

    1.Flex SDK 4.1 兼容性 Flex SDK 4.1 兼容 Flash Builder 4.0 ,因此在 Flash Builder 4.0 中使用 4.1 SDK 时可以使用设计视图 Fl ...

  2. nginx已经启动 无法访问页面

    通过IP访问,可以看到  welcome nginx 的提示 下面我重启linux服务器,重启后通过ip访问,死活连接不上了?没办法了,只有在百度和google 最后发现问题不是出在nginx上,而是 ...

  3. 前端代码tomcat下简单部署

    软件 filezilla [ftp]  +  visionapp Remote Desktop[远程桌面] (前提:前后端代码分离,如前端angular实现) ftp上传到机器{软件 filezill ...

  4. 学习使用turtlebot2——ROS上安装turtlebot2

    安装环境: 安装Ubuntu 14.04版本和ROS Indigo 参考:http://wiki.ros.org/turtlebot/Tutorials 安装步骤     有两种安装方法,一种直接的安 ...

  5. Elasticsearch集群 管理

    第7章 深入Elasticsearch集群 启动一个Elasticsearch节点时,该节点会开始寻找具有相同集群名字并且可见的主节点.如 果找到主节点,该节点加入一个已经组成了的集群:如果没有找到, ...

  6. HTML布局四剑客-Flex,Grid,Table,Float

    前言 在HTML布局中有很多的选择,同一种表现方式可以使用不同的方法来实现.下面来对四种最常见的布局方式进行阐述和解释,它们分别是Float,Table,Grid和Flex Float 第一位出场的就 ...

  7. Java保留字和关键字

    Java的关键字:java的关键字对Java的编译器有着特殊的意义,它们表示一种数据类型或者程序的结构相关. Java的保留字:为Java预留的关键字,现在还没用到,但在新Java的版本升级中可能会用 ...

  8. ViewPager学习及使用(一)

    一:基础篇 1.ViewPager的简介和作用ViewPager是android扩展包v4包中的类,这个类可以让用户左右切换当前的view1)ViewPager类直接继承了ViewGroup类,所有它 ...

  9. R语言操作mysql上亿数据量(ff包ffbase包和ETLUtils包)

    平时都是几百万的数据量,这段时间公司中了个大标,有上亿的数据量. 现在情况是数据已经在数据库里面了,需要用R分析,但是完全加载不进来内存. 面对现在这种情况,R提供了ff, ffbase , ETLU ...

  10. 35个例子学会find

    find的使用格式如下: $ find <指定目录> <指定条件> <指定动作> - <指定目录>: 所要搜索的目录及其所有子目录.默认为当前目录. - ...