解析ThreadLocal

本文深入探讨了Java中ThreadLocal的概念、作用和应用实例,包括如何使用ThreadLocal实现线程安全的单例、上下文信息管理以及在实际业务场景中的具体应用。同时,介绍了ThreadLocal在JavaEE领域的高级应用,特别是在Hibernate中的优化使用方法,以及如何正确区分和利用openSession与getCurrentSession的区别。最后,阐述了ThreadLocal在不同技术领域的应用案例,如在移动开发、大数据开发等场景中的实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. ThreadLocal介绍

    在Java中,ThreadLocal是摆脱不易变类实现线程安全的另一种方式。如果你用Java写过多线程或是并发的代码,你必定熟悉synchronization 或是Locking极大影响系统的可扩展性的高昂代价,然而你除了使用synchronization为多线程中分享对象别无选择。ThreadLocal为Java提供了另一种方式完成线程安全,它没有解决同步的需要,取而代之是通过提供给每个线程具体独立的变量副本来消除共享。其结果能够提高程序的可扩展性以及性能的提升。在类中,ThreadLocal实例是典型静态域,用于关联到一个线程的状态(比如用户ID 或者事物ID)。一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。由于Java中编写线程局部变量的代码相对其它语言(比如, FORTRAN)来说要笨拙一些,造成线程局部变量对于Java程序员来说不是那么熟悉。

    例如,如下类对每个线程产生独一无二的本地标识。一个线程调用UniqueThreadIdGenerator.getCurrentThreadId()时它的ID被赋值,在它的序列调用中保持不变。只用线程处于活跃状态,ThreadLocal实例就能够访问。当线程消亡,线程对应的线程变量副本就会被垃圾回收。

 

[java]  view plain copy print ?
  1. import java.util.concurrent.atomic.AtomicInteger;  
  2.   
  3.   
  4. public class UniqueThreadIdGenerator {  
  5.   
  6.   
  7.     private static final AtomicInteger uniqueId = new AtomicInteger(0);  
  8.   
  9.   
  10.     private static final ThreadLocal < Integer > uniqueNum =   
  11.         new ThreadLocal < Integer > () {  
  12.             @Override protected Integer initialValue() {  
  13.                 return uniqueId.getAndIncrement();  
  14.         }  
  15.     };  
  16.   
  17.     public static int getCurrentThreadId() {  
  18.         return uniqueId.get();  
  19.     }  
  20. // UniqueThreadIdGenerator  


 2. ThreadLocal的应用

        什么时候使用ThreadLocal呢?许多Java程序员可能会问。ThreadLocal 有许多真正要使用的情况,那也是为什么ThreadLocal被采纳为Java平台标准的库。如果你没有并发编程的实践,你可能不太熟悉ThreadLocal的使用。下面列举一些ThreadLocal众所周知的使用的情形。
  1. ThreadLocal实现每个线程单例(Thread Singleton)或是像事务ID一样的每个线程上下文(Context)信息,这种实现特别棒。
  2. 能够将线程不安全的独享放到ThreadLocal中,这个线程安全的对象就会突然变得线程安全。
  3. ThreadLocal提供了另一种方式来扩展线程。通过ThreadLocal,你可以保护或是承载信息从一个方法到另一个你能够承载的方法。你没有必要去修改任何方法就能实现你所需要的功能,拥有了极大的可扩展性。
  下面有个例子,详细地说明了ThreadLocal存储线程范围的变量。
  创建IContext.java类,设置上下文事务ID值。
  
[java]  view plain copy print ?
  1. public class IContext {  
  2.     private String transactionId = null;  
  3.   
  4.     public String getTransactionId() {  
  5.         return transactionId;  
  6.     }  
  7.   
  8.     public void setTransactionId(String transactionId) {  
  9.         this.transactionId = transactionId;  
  10.     }  
  11. }  
    IThreadLocal类包含ThreadLocal对象实例set/get操作。
[java]  view plain copy print ?
  1. public class IThreadLocal {  
  2.     public static final ThreadLocal myThreadLocal = new ThreadLocal();  
  3.   
  4.     public static void set(IContext context){  
  5.         myThreadLocal.set(context);  
  6.     }  
  7.   
  8.     public static void remove(IContext context){  
  9.         myThreadLocal.remove();  
  10.     }  
  11.   
  12.     public static IContext get(){  
  13.         return (IContext)myThreadLocal.get();  
  14.     }  
  15. }  

   定义具体的业务处理逻辑,如下IBusinessSerive.java所示。
   
[java]  view plain copy print ?
  1. public class IBusinessService {  
  2.   
  3.     Logger logger = LoggerFactory.getLogger(IBusinessService.class);  
  4.     public void callService(){  
  5.         IContext myContext = IThreadLocal.get();  
  6.         logger.info("current transaction ID :"+ myContext.getTransactionId());  
  7.     }  
  8.   
  9. }  
   使用线程测试上面定义的所有类。
[java]  view plain copy print ?
  1. public class ThreadLocalMain  extends Thread{  
  2.   
  3.     @Override  
  4.     public void run(){  
  5.         IContext context = new IContext();  
  6.         context.setTransactionId("threadName: "+getName()+ ", id:" + getId()+", priority:" + getPriority() );  
  7.         IThreadLocal.set(context);  
  8.         new IBusinessService().callService();  
  9.         IThreadLocal.remove(context);  
  10.     }  
  11.   
  12.   
  13.   
  14.     public static void main(String []args){  
  15.         Thread threadOne = new ThreadLocalMain();  
  16.         threadOne.start();  
  17.         try {  
  18.             Thread.sleep(1000);  
  19.         } catch (InterruptedException e) {  
  20.             e.printStackTrace();  
  21.         }  
  22.         Thread threadTwo = new ThreadLocalMain();  
  23.         threadTwo.start();  
  24.     }  
  25. }  

   结果如下:
[html]  view plain copy print ?
  1. Connected to the target VM, address: '127.0.0.1:3004', transport: 'socket'  
  2. 2013-04-26 22:18:20,613 INFO  (org.java.IBusinessService:14) - current transaction ID :threadName: Thread-0, id:12, priority:5  
  3. Disconnected from the target VM, address: '127.0.0.1:3004', transport: 'socket'  
  4. 2013-04-26 22:18:21,320 INFO  (org.java.IBusinessService:14) - current transaction ID :threadName: Thread-1, id:13, priority:5  

3. ThreadLocal在JavaEE中的应用

    ThreadLocal在Hibernate中得到充分的使用,特别是对于事物的处理上。比如, getCurrentSession非常好,不需要我们自己写ThreadLocal只需要在hibernate.cfg的配置文件中声明一下便可获得ThreadLocal的好处是便于我们划分我们的程序的层次与封装,带也带来了一定的性能问题。 特别是“如果你使用的是getCurrentSession,那么就算你是一个简单的select语句,也必须包含在事务块中”。碰到这种情况,可以采取如下手段:
  1. 一个service方法中只有单个dao操作且此操作是一个select类的操作,请使用openSession,并且即时在finally块中关闭它;
  2. 如果一个service方法中涉及到多个dao操作,请一定使用getCurrentSession;
  3. 如果一个service方法中混合着select操作,delete, update, insert操作。请按照下述原则:                               
        a. 将属于select的操作,单独做成一个dao方法,该dao使用openSession并且在finally块中及时关闭session,
            该dao只需要返回一个java的object如:List<Student>即可,如果出错将exception抛回给调用它的service
            方法。
       b. 对于其它的delete, insert, update的dao操作,请使用getCurrentSession。
       c. 忌讳,把select类的操作放在“事务”中;


    

         4. openSession与getCurrentSession的区别
            a.  openSession一旦被调用,必须且一定要在finally块中close,要不然你就等着out of memory吧;

      b.  如果你使用的是getCurrentSession,那么你不能在finally块中调用”session.close()”,不行你可以在finally块中用try-catch把session.close();包起来,然后在catch{}块中抛出这个exception,这个exception将会是:sessionhas been already closed。其原因是你用的是getCurrentSession,那么它在session.commit()或者是session.rollback()时就已经调用了一次session.close()了,因此你只要正确放置session.commit()与rollback()即可。另外,你可以在finally块中调用”ThreadLocalSessionContext.unbind(factory);”,以使得当前的事务结束时把session(即dbconnection)还回db connection pool中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值