关于java:简单理解ThreadLocal

2次阅读

共计 5429 个字符,预计需要花费 14 分钟才能阅读完成。

ThreadLocal 类的介绍

ThreadLocal,从外表上读英文的意思为线程本地变量,这样兴许更好了解了,就是每个线程本人独有的,不与其它线程共享的变量。

罕用的就这几个,俩外部类,四个办法。

  • get()办法是用来获取 ThreadLocal 在以后线程中保留的变量正本。
  • set()用来设置以后线程中变量的正本。
  • remove()用来移除以后线程中变量的正本。
  • initialValue()是一个 protected 办法,个别是用来在应用时进行重写的,它是一个提早加载办法。ThreadLocal 没有被以后线程赋值时或以后线程刚调用 remove 办法后调用 get 办法,返回此办法值。

举例:

定义两个不同工作的线程,别离向各自的本地变量中寄存值,见证两个线程本地变量中的内容是互不烦扰的。

public class MyThreadLocal {
    // 采纳匿名外部类的形式来重写 initialValue 办法
    private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>() {
        /**
         * ThreadLocal 没有被以后线程赋值时或以后线程刚调用 remove 办法后调用 get 办法,返回此办法值
         */
        @Override
        protected Object initialValue() {System.out.println("调用 get 办法时,以后线程共享变量没有设置,调用 initialValue 获取默认值!");
            return null;
        }
    };

    // 操纵 int 类型的工作线程
    public static class MyIntegerTask implements Runnable {
        private String name;

        MyIntegerTask(String name) {this.name = name;}

        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();
                }
            }
        }
    }

    // 操纵 string 类型的工作线程
    public static class MyStringTask implements Runnable {
        private String name;
        MyStringTask(String name) {this.name = name;}
        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();
                }
            }
        }
    }

    public static void main(String[] args) {new Thread(new MyIntegerTask("IntegerTask1")).start();
        new Thread(new MyStringTask("StringTask1")).start();}

}

运行后果:

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

get 办法剖析

波及到的源码:

get(); 办法:供 ThreadLocal 对象来调用

public T get() {Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {@SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();}

getMap(); 办法:这个办法是返回以后线程 t 中的一个成员变量 threadLocals,它是 Thread 类中的一个外部类

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

看一下 ThreadLocalMap 的实现:能够看到 ThreadLocalMap 的 Entry 继承了 WeakReference(弱援用类),并且应用 ThreadLocal 作为键值。

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {super(k);
                value = v;
            }
        }。。。。。。。。。}

getEntry(); 办法

private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
}

setInitialValue(); 办法:就是如果 map 不为空,就设置键值对,为空,再创立 Map

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;
    }

createMap(); 办法:如果 map 为空,就初始化 ThreadLocalMap,哈哈明确了~

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

不难看出,get()办法,首先获取以后的线程,而后通过 getMap(t)办法获取到一个 map,map 的类型为 ThreadLocalMap。而后接着上面获取到 <key,value> 键值对,如果获取胜利,则返回 value 值,如果 map 为空,则调用 setInitialValue 办法返回 value。

<!–more–>

测试完下面的例子,看完 get 办法的实现,应该明确 ThreadLocal 是怎么个原理了,大抵如下 (就是这样的):首先,在每个线程 Thread 外部有一个 ThreadLocal.ThreadLocalMap 类型的成员变量 threadLocals,这个 threadLocals 就是用来存储理论的变量的,键值为以后 ThreadLocal 变量,value 为变量(比如说下面定义的 String 变量或者 Integer 变量)。初始时,在 Thread 外面,threadLocals 为空,当通过 ThreadLocal 变量调用 get() 办法或者 set()办法,就会对 Thread 类中的 threadLocals 进行初始化,并且以以后 ThreadLocal 变量为键值,以 ThreadLocal 要保留的变量为 value,存到 threadLocals。如果要应用正本变量,就能够通过 get 办法在 threadLocals 外面查找。

<!–more–>

这种存储构造的益处:

  1. 线程死去的时候,线程共享变量 ThreadLocalMap 则销毁。
  2. ThreadLocalMap<ThreadLocal,Object> 键值对数量为 ThreadLocal 的数量,一般来说 ThreadLocal 数量很少,相比在 ThreadLocal 中用 Map<Thread, Object> 键值对存储线程共享变量(Thread 数量一般来说比 ThreadLocal 数量多),性能进步很多。

ThreadLocal 的利用场景

最常见的 ThreadLocal 应用场景为 用来解决 数据库连贯、Session 治理等。

比方以下,代码来自:https://www.iteye.com/topic/1…

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;
}

对于 ThreadLocal 导致的内存透露问题,和解决办法

  1. 每个 thread 中都存在一个 map,map 的类型是 ThreadLocal.ThreadLocalMap。Map 中的 key 为一个 threadlocal 实例。这个 Map 确实应用了弱援用,不过弱援用只是针对 key。每个 key 都弱援用指向 threadlocal,当把 threadlocal 实例置为 null 当前, 没有任何强援用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。然而 value 却不能回收,因为存在一条从 currentThread 连贯过去的强援用。只有以后 thread 完结当前,currentThread 才不会存在栈中,强援用断开,CurrentThread,Map,value 将全副被 GC 回收。
  2. 所以得出一个论断就是只有这个线程对象被 gc 回收,就不会呈现内存泄露,但在 threadLocal 设为 null 和线程完结这段时间不会被回收的,就产生了咱们认为的内存泄露。其实这是一个对概念了解的不统一,也没什么好争执的。最要命的是线程对象不被回收的状况,这就产生了真正意义上的内存泄露。比方应用线程池的时候,线程完结是不会销毁的,会再次应用的。就可能呈现内存泄露。
  3. Java 为了最小化缩小内存泄露的可能性和影响,在 ThreadLocal 的 get,set 的时候都会革除线程 Map 里所有 key 为 null 的 value。所以最怕的状况就是,threadLocal 对象设 null 了,开始产生“内存泄露”,而后应用线程池,这个线程完结,线程放回线程池中不销毁,这个线程始终不被应用,或者调配应用了又不再调用 get,set 办法,那么这个期间就会产生真正的内存泄露。

个别有两种办法:

  1. 应用完线程共享变量后,显示调用 ThreadLocalMap.remove 办法革除线程共享变量
  2. JDK 倡议 ThreadLocal 定义为 private static,这样 ThreadLocal 的弱援用问题则不存在了。
正文完
 0