接触java是在5年前的秋天,距今也有5个年头了,以前对java 的学习只是停留在一知半解的水平,对用的比较多的工具类和算法接口比较熟悉,其他那些平时不经常使用的类或者语法就只能是眼熟了,比如说ThreadLocal等,在接下来的学习工作中,我会慢慢的一个一个的学习和分享,那么就让我们来学习一下ThreadLocal吧。
初始印象:TheadLocal给我的第一感觉就是用来解决线程同步问题的,如果多个线程共同享用一个变量,其中一个线程修改了这个变量就会影响其他的线程对这个变量的访问。ThreadLocal可以使得每个变量在每个线程中都有一份,互不影响,也就是说每个线程中都有一份变量的副本。接下来让我们看个例子:
class DBUtil{
private static String userName;
private static String password;
private static String dbUrl;
private static
Connection connect = null
;
static{
try{
ClassLoader classLoader = DBConnection.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("config/props/db.properties");
Properties props = new Properties();
props.load(is);
dbUrl = props.getProperty("dbUrl");
userName= props.getProperty("userName");
password = props.getProperty("password");
Class.forName(props.getProperty("driver"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("");
}
public static
Connection openConnection() {
if
(connect ==null
){
connect = DriverManager.getConnection(url, user, password);
}
return
connect;
}
throws Exception {
if (conn != null) {
conn.close();
}
}
}
这是一个数据库访问工具类,在单线程的情况下使用没有任何问题,如果在多个线程的环境中,就会产生问题,1、此类中的方法没有做线程同步控制,在openConnection方法中有可能会打开多个数据库连接。2、如果一个线程正在使用数据库连接,另一个线程完全可以关闭此链接,所以会产生意想不到的问题,哪咱们怎么解决这些问题呢?
我们可以使用一些方法来对此做同步控制,这样的话就会大大降低数据库访问效率,其中一个使用,另一个线程必须无条件等待;如果每个线程在使用的时候都去重新打开一个新的数据库连接
可以解决这种问题,但是将会带来内存的消耗,性能下降,服务器压力非常大,频繁的打开关闭重资源的操作。还有没有其他的解决方法呢?
这个时候ThreadLocal就可以上场了。先看代码:
class DBUtil{
private staict ThreadLocal<Connection> connectionHolder=new ThreadLocal<Connection>(){
@Override
protected Connection initialValue() {
Connection connection = null;
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return connection;
}
};
public static Connection getConnection() throws Exception {
return connectionHolder.get();
}
public static void close(ResultSet rs, Statement stat, Connection conn)
throws Exception {
if (rs != null) {
rs.close();
}
if (stat != null) {
stat.close();
}
if (conn != null) {
conn.close();
connectionHolder.remove();
}
}
}
为什么这样就能解决以上出现的问题呢?下面深入剖析一个ThreadLocal源码
提供的方法;get()、set()、initialValue()、remove();get用来获取当前线程中变量的副本,set用来设置当前线程中变量的副本,remove用来删除当前变量的副本,initValue用来初始化当前线程副本,它是一个
延迟加载方法,一般是用来重写的。
get源码:
public T get(){
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map!=null){
ThreadLocalMap.Entry e=map.getEntry(this);//这个地方传进去的是this,not 当前线程
if(e!=null){
return (T)e.value;
return setInitialValue();
}} }
ThreadLocalMap getMap(Thread t){
return t.threadLocals;
}
class Thread{
ThreadLocal.ThreadLocalMap threadLocals = null;
}
staitic class ThreadLocalMap {
staitic class Entry extends WeakReference<ThreadLocal>{
object value;
public Entry (ThreadLocal k,object v){
super(k);
value=v;
}
}
}
private T setInitialValue(){
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(t!=null){
map.set(this,value);
else
createMap(t,value);
return value;
}
}
void createMap(Thread t,T firstValue){
t.threadLocals = new ThreadLocalMap(this,firstValue);
}
看到这里,我相信大家已经明白ThreadLocal为每个线程保存一份变量副本的原理了,具体就是使用当前线程threadLocals 来实现的,用当前ThreadLocal对象为key,变量副本为值
但是TreadLocal并不能起到共享变量的作用,也就是说此变量是一个副本,每一个线程使用时,都需要重新创建。例如如果此变量是一个打开数据库的连接
每个线程使用这个连接时,都需要重新getConnection