码迷,mamicode.com
首页 > 其他好文 > 详细

ThreadLocal

时间:2015-08-31 19:18:24      阅读:231      评论:0      收藏:0      [点我收藏+]

标签:

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

 

 

 另一个例子:

class TreadLocalDemo {

    static ThreadLocal<HashMap> map0 = new ThreadLocal<HashMap>() {
        @Override
        protected HashMap initialValue() {
            System.out.println(Thread.currentThread().getName() + "initialValue");
            return new HashMap();
        }
    };

    public void run() {
        Thread[] runs = new Thread[3];
        for (int i = 0; i < runs.length; i++) {
            runs[i] = new Thread(new T1(i));
        }
        for (int i = 0; i < runs.length; i++) {
            runs[i].start();
        }
    }

    public static class T1 implements Runnable {
        int id;

        public T1(int id0) {
            id = id0;
        }

        public void run() {
            System.out.println(Thread.currentThread().getName() + ":start");
            HashMap map = map0.get();
            for (int i = 0; i < 10; i++) {
                map.put(i, i + id * 100);
                try {
                    Thread.sleep(100);
                } catch (Exception ex) {
                }
            }
            System.out.println(Thread.currentThread().getName() + ‘:‘ + map);
        }
    }
    
    public static void main(String[] args) {
        TreadLocalDemo test = new TreadLocalDemo();
        test.run();
    }
}

 运行结果:

Thread-0:start
Thread-0initialValue
Thread-1:start
Thread-1initialValue
Thread-2:start
Thread-2initialValue
Thread-0:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}
Thread-1:{0=100, 1=101, 2=102, 3=103, 4=104, 5=105, 6=106, 7=107, 8=108, 9=109}
Thread-2:{0=200, 1=201, 2=202, 3=203, 4=204, 5=205, 6=206, 7=207, 8=208, 9=209}

 

 

 

 

 

部分摘自 :http://www.cnblogs.com/dolphin0520/p/3920407.html

ThreadLocal

标签:

原文地址:http://www.cnblogs.com/balfish/p/4773589.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!