前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中ThreadLocal类的作用以及实现原理

Java中ThreadLocal类的作用以及实现原理

原创
作者头像
IT工作者
发布2022-05-09 16:29:42
3310
发布2022-05-09 16:29:42
举报
文章被收录于专栏:程序技术知识

一、

无论如何,要编写一个多线程安全(Thread-safe)的程序是困难的,为了让线程共享资源,必须小心地对共享资源进行同步,同步带来一定的效能延迟,而另一方面,在处理同步的时候,又要注意对象的锁定与释放,避免产生死结,种种因素都使得编写多线程程序变得困难。

尝试从另一个角度来思考多线程共享资源的问题,既然共享资源这么困难,那么就干脆不要共享,何不为每个线程创造一个资源的复本。将每一个线程存取数据的行为加以隔离,实现的方法就是给予每个线程一个特定空间来保管该线程所独享的资源。

二、什么是ThreadLocal?

顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

使用场景

To keep state with a thread (user-id, transaction-id, logging-id)

To cache objects which you need frequently

三、ThreadLocal类

它主要由四个方法组成initialValue(),get(),set(T),remove(),其中值得注意的是initialValue(),该方法是一个protected的方法,显然是为了子类重写而特意实现的。该方法返回当前线程在该线程局部变量的初始值,这个方法是一个延迟调用方法,在一个线程第1次调用get()或者set(Object)时才执行,并且仅执行1次。ThreadLocal中的确实实现直接返回一个null:

四、ThreadLocal的原理

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现:

代码语言:javascript
复制
public class ThreadLocal
{
  private Map values = Collections.synchronizedMap(new HashMap());
  public Object get()
  {
    Thread curThread = Thread.currentThread();
    Object o = values.get(curThread);
    if (o == null && !values.containsKey(curThread))
    {
       o = initialValue();
       values.put(curThread, o);
    }
    return o;
 }

 public void set(Object newValue)
 {
    values.put(Thread.currentThread(), newValue);
 }

 public Object initialValue()
 {
    return null;
 }
}

五、ThreadLocal 的使用

使用方法一:

Hibernate的文档时看到了关于使ThreadLocal管理多线程访问的部分。具体代码如下

代码语言:javascript
复制
  public static final ThreadLocal session = new ThreadLocal();
     public static Session currentSession() {
        Session s = (Session)session.get();
        //open a new session,if this session has none
     if(s == null){
         s = sessionFactory.openSession();
         session.set(s);
      }
          return s;
    }

我们逐行分析

   1。 初始化一个ThreadLocal对象,ThreadLocal有三个成员方法 get()、set()、initialvalue()。

    如果不初始化initialvalue,则initialvalue返回null。

   3。 session的get根据当前线程返回其对应的线程内部变量,也就是我们需要的net.sf.hibernate.Session(相       当于对应每个数据库连接).多线程情况下共享数据库链接是不安全的。ThreadLocal保证了每个线程都有自己       的s(数据库连接)。

    5。如果是该线程初次访问,自然,s(数据库连接)会是null,接着创建一个Session,具体就是行6。

    6。创建一个数据库连接实例 s

    7。保存该数据库连接s到ThreadLocal中。

    8。如果当前线程已经访问过数据库了,则从session中get()就可以获取该线程上次获取过的连接实例。

使用方法二

 当要给线程初始化一个特殊值时,需要自己实现ThreadLocal的子类并重写该方法,通常使用一个内部匿名类对       ThreadLocal进行子类化,EasyDBO中创建jdbc连接上下文就是这样做的:

代码语言:javascript
复制
 public class JDBCContext{
     private static Logger logger = Logger.getLogger(JDBCContext.class);
     private DataSource ds;
     protected Connection connection;
     private boolean isValid = true;
     private static ThreadLocal jdbcContext;
 
     private JDBCContext(DataSource ds){
       this.ds = ds;
       createConnection(); 
     }
    public static JDBCContext getJdbcContext(javax.sql.DataSource ds)
    { 
      if(jdbcContext==null)jdbcContext=new JDBCContextThreadLocal(ds);
      JDBCContext context = (JDBCContext) jdbcContext.get();
      if (context == null) {
       context = new JDBCContext(ds);
     }
      return context;
   }

  private static class JDBCContextThreadLocal extends ThreadLocal {
     public javax.sql.DataSource ds;
    public JDBCContextThreadLocal(javax.sql.DataSource ds)
   {
     this.ds=ds;
   }
  protected synchronized Object initialValue() {
   return new JDBCContext(ds);
  }
 }
}

使用单例模式,不同的线程调用getJdbcContext()获得自己的jdbcContext,都是通过JDBCContextThreadLocal 内置子类来获得JDBCContext对象的线程局部变量

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档