zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    “<”特殊符号写法
    js中,符合属性的js写法是讲下横杆去掉
    Windows 搭建WAMP+Mantis
    Windows server 2012 R2 服务器用户自动锁定
    对域用户设置为本地管理员权限
    windows 域控用户记住最后一次登录用户名
    redhat7.6 配置主从DNS
    redhat7.6 DNS配置正向解析
    redhat7.6 AIDE 系统文件完整性检查工具
    redhat7.6 httpd 匿名目录 目录加密 域名跳转
  • 原文地址:https://www.cnblogs.com/xiaxj/p/9634233.html
Copyright © 2011-2022 走看看