您的位置:首页 > 其它

ThreadLocal用法和实现原理

2014-09-05 09:39 232 查看
如果你定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap。并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在跨线程的意义。那么你不要sychronize这么复杂的东西,ThreadLocal将是你不错的选择。

举例来说:

import java.util.HashMap;

public class TreadLocalTest {

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

        } 

    } 

    /** 

     * Main 

     * @param args 

     */ 

    public static void main(String[] args){ 

        TreadLocalTest test = new TreadLocalTest(); 

        test.run(); 

    }

}

输出解释;

Thread-1:start 

Thread-2:start 

Thread-0:start 

Thread-2initialValue 

Thread-1initialValue 

Thread-0initialValue 

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} 

Thread-0:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}

可以看到map0 虽然是个静态变量,但是initialValue被调用了三次,通过debug发现,initialValue是从map0.get处发起的。而且每个线程都有自己的map,虽然他们同时执行。

进入Theadlocal代码,可以发现如下的片段;

public T get() { 

        Thread t = Thread.currentThread(); 

        ThreadLocalMap map = getMap(t); 

        if (map != null) { 

            ThreadLocalMap.Entry e = map.getEntry(this); 

            if (e != null) 

                return (T)e.value; 

        } 

        return setInitialValue(); 

    }

这说明ThreadLocal确实只有一个变量,但是它内部包含一个map,针对每个thread保留一个entry,如果对应的thread不存在则会调用initialValue。

具体用法:

用法一:在关联数据类中创建private static ThreadLocal

ThreaLocal的JDK文档中说明:ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread。如果我们希望通过某个类将状态(例如用户ID、事务ID)与线程关联起来,那么通常在这个类中定义private static类型的ThreadLocal 实例。

例如,在下面的类中,私有静态 ThreadLocal 实例(serialNum)为调用该类的静态 SerialNum.get() 方法的每个线程维护了一个“序列号”,该方法将返回当前线程的序列号。(线程的序列号是在第一次调用 SerialNum.get() 时分配的,并在后续调用中不会更改。)

public class SerialNum {  

    // The next serial number to be assigned  

    private static int nextSerialNum = 0;  

  

    private static ThreadLocal serialNum = new ThreadLocal() {  

        protected synchronized Object initialValue() {  

            return new Integer(nextSerialNum++);  

        }  

    };  

  

    public static int get() {  

        return ((Integer) (serialNum.get())).intValue();  

    }  

}  

【例】



 
public class ThreadContext {

 
  private String userId;
  private Long transactionId;

 
  private static ThreadLocal threadLocal = new ThreadLocal(){

    @Override

        protected ThreadContext initialValue() {

            return new ThreadContext();

        }

 

  };
  public static ThreadContext get() {

    return threadLocal.get();

  }

  public String getUserId() {

    return userId;

  }
  public void setUserId(String userId) {

    this.userId = userId;

  }
  public Long getTransactionId() {

    return transactionId;

  }
  public void setTransactionId(Long transactionId) {

    this.transactionId = transactionId;

  }

 

}

 

用法二:在Util类中创建ThreadLocal

这是上面用法的扩展,即把ThreadLocal的创建放到工具类中。

【例】例如Hibernate的工具类:



public class HibernateUtil {

    private static Log log = LogFactory.getLog(HibernateUtil.class);

    private static final SessionFactory sessionFactory;     //定义SessionFactory

 

    static {

        try {

            // 通过默认配置文件hibernate.cfg.xml创建SessionFactory

            sessionFactory = new Configuration().configure().buildSessionFactory();

        } catch (Throwable ex) {

            log.error("初始化SessionFactory失败!", ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //创建线程局部变量session,用来保存Hibernate的Session

    public static final ThreadLocal session = new ThreadLocal();

 

    /**

     * 获取当前线程中的Session

     * @return Session

     * @throws HibernateException

     */

    public static Session currentSession() throws HibernateException
{

        Session s = (Session) session.get();

        // 如果Session还没有打开,则新开一个Session

        if (s == null) {

            s = sessionFactory.openSession();

            session.set(s);         //将新开的Session保存到线程局部变量中

        }

        return s;

    }

 

    public static void closeSession() throws HibernateException
{

        //获取线程局部变量,并强制转换为Session类型

        Session s = (Session) session.get();

        session.set(null);

        if (s != null)

            s.close();

    }

}

 

 


用法三:在Runnable中创建ThreadLocal

 还有一种用法是在线程类内部创建ThreadLocal,基本步骤如下:

1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。 

2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。 

3、在ThreadDemo类的run()方法中,通过调用getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。 

 



public class ThreadLocalTest implements Runnable{

    

    ThreadLocal<Studen> studenThreadLocal = new ThreadLocal<Studen>();

    @Override

    public void run() {

        String currentThreadName = Thread.currentThread().getName();

        System.out.println(currentThreadName + " is running...");

        Random random = new Random();

        int age = random.nextInt(100);

        System.out.println(currentThreadName + " is set age: "  + age);

        Studen studen = getStudent(); //通过这个方法,为每个线程都独立的new一个student对象,每个线程的的student对象都可以设置不同的值

        studen.setAge(age);

        System.out.println(currentThreadName + " is first get age: " + studen.getAge());

        try {

            Thread.sleep(500);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        System.out.println( currentThreadName + " is second get age: " + studen.getAge());

        

    }

    

    private Studen getStudent() {

        Studen studen = studenThreadLocal.get();

        if (null == studen) {

            studen = new Studen();

            studenThreadLocal.set(studen);

        }

        return studen;

    }

    public static void main(String[] args) {

        ThreadLocalTest t = new ThreadLocalTest();

        Thread t1 = new Thread(t,"Thread A");

        Thread t2 = new Thread(t,"Thread B");

        t1.start();

        t2.start();

    }

    

}

class Studen{

    int age;

    public int getAge() {

        return age;

    }

    public void setAge(int age) {

        this.age = age;

    }

    

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: