• ThreadLocal


    ThreadLocal很多地方叫做线程本地变量,也有线程本地存储的叫法,它为变量在每个线程中创建一个副本,那么每个线程可以访问自己内部的副本变量

    ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制

    比如数据库连接的例子:

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

    上面的代码在多线程中使用有线程安全问题,因为connect是共享变量,而两个方法使用共享变量的方法都没有进行同步.

    如果两个方法都进行同步处理,而且在在调用connect的地方进行了同步处理确实可以解决这个问题,但是这样会大大影响程序执行的效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待.

    那么事实上,这里是不需要将connect变量共享的,因为一个线程不关心其他线程是否对这个connect进行了修改.

    那么既然不需要共享,就可以不用static的,用实例的方式在每个需要使用数据库连接的地方调用,使用后再释放这个连接,如下

    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 类提供的几个方法:

    public T get() { }
    public void set(T value) { }
    public void remove() { }
    protected T initialValue() { }

    get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,

    set()用来设置当前线程中变量的副本,

    remove()用来移除当前 线程中变量的副本,

    initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。

    ThreadLocal为每个线程创建变量的副本的方法:

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

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

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

    下面简单的例子说明通过ThreadLocal能达到在每个线程中创建变量副本的效果

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

    输出结果:

    1
    main
    9
    Thread-0
    1
    main

    从这段代码的输出结果可以看出,在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。

    看上面的anchor,也就是test.set(),不写会报NullPointerException,而用下面的写法不写set方法也可

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

    应用场景:

    最常见的使用场景是用来解决数据库连接,session管理等

    private static ThreadLocal<Connection> connectionHolder
    = new ThreadLocal<Connection>() {
    public Connection initialValue() {
        return DriverManager.getConnection(DB_URL);
    }
    };
     
    public static Connection getConnection() {
    return connectionHolder.get();
    }

    下面来看一个hibernate中典型的ThreadLocal的应用

    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;
    }
  • 相关阅读:
    Eclipse MarketPlace 打不开,对话框闪退
    docker 创建容器的时候的坑
    win7 设置docker加速器
    postgres常用命令
    docker加速器配置
    docker 安装 postgresql
    Spring Cloud-服务的注册与发现之服务注册中心(Eureka Server)
    redis incr自增指定的无限期 key 删除问题
    redis读取自增时候指定的key问题
    docker 安装 redis
  • 原文地址:https://www.cnblogs.com/balfish/p/4773589.html
Copyright © 2020-2023  润新知