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;
}
原文地址:https://www.cnblogs.com/balfish/p/4773589.html