• threedLocal设计原因及详解


    ThreedLocal在中文的翻译中应该翻译成:线程局部变量。

    1:设计的原因

           在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal可以很好的解决多线程操作统一数据造成不一致情况。

    public final class ConnectionUtil {
    
        private ConnectionUtil() {}
    
        private static final ThreadLocal<Connection> conn = new ThreadLocal<>();
    
        public static Connection getConn() {
            Connection con = conn.get();
            if (con == null) {
                try {
                    Class.forName("com.mysql.jdbc.Driver");
                    con = DriverManager.getConnection("url", "userName", "password");
                    conn.set(con);
                } catch (ClassNotFoundException | SQLException e) {
                    // ...
                }
            }
            return con;
        }
    
    }

    这样子,每次连接,都会重新连接一个新的变量副本。

    那问题来了?

    1、每个线程的变量副本是存储在哪里的?及如何获取和设置线程局部变量?

    参考java源码可以看见,

    1:每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。

    2:当我们需要改变或者获得该线程内部的变量时,java提供了get()方法

      这个获取的过程如下

    1.  获取当前线程
    2.     然后获取当前线程中的ThreadLocalMap对象而且,ThreadLocalMap的内部使用一个Entry<ThreadLocal , T>数据结构来向来保存的,
    3.    判断Entry对象是否为空,如果不为空,则直接获取ThreadLocal中的value,如果为空,则调用setInitialValue()方法对value进行初始化(初始化到ThreadLocal中)
    //get()用户获取次当前线程副本值中的线程局部变量。
    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);     //这里面this指的是threadLocal本身
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }

    对线程局部变量进行初始化:

    //对线程局部变量初始化
    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;
        }

    那么如何设置当前线程中的线程局部变量呢?将值设置进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);
        }

    2:应用场景

    当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。

    3:对于内存泄漏情况

    由上面的get()和set()方法知道,因为Entry本身内存是继承自WeakReference,这样当ThreadLocal不再被引用时,因为弱引用机制原因,当jvm发现内存不足时,会自动回收弱引用指向的实例内存,即其线程内部的ThreadLocalMap会释放其对ThreadLocal的引用从而让jvm回收ThreadLocal对象。这里是重点强调下,是回收对ThreadLocal对象,而非整个Entry,所以线程变量中的值T对象还是在内存中存在的,所以内存泄漏的问题还没有完全解决。接着分析JDK的实现,会发现在调用ThreadLocal.get()或者ThreadLocal.set(T)时都会定期执行回收无效的Entry操作。所以这就解决了上述问题中的内存泄漏问题。

    参考文件:

    https://www.cnblogs.com/jcli/p/talk_about_threadlocal.html

    https://blog.csdn.net/u013256816/article/details/51776846

  • 相关阅读:
    IIS的各种身份验证详细测试
    HTTP Error 401.3 Unauthorized Error While creating IIS 7.0 web site on Windows 7
    C/S and B/S
    WCF ContractFilter mismatch at the EndpointDispatcher exception
    Configure WCF
    Inheritance VS Composition
    Unhandled Error in Silverlight Application, code 2103 when changing the namespace
    Java RMI VS TCP Socket
    Principles Of Object Oriented Design
    Socket处理发送和接收数据包,一个小实例:
  • 原文地址:https://www.cnblogs.com/xiaxj/p/9634233.html
Copyright © 2020-2023  润新知