深入学习java并发编程:ThreadLocal<T>实现

来源:互联网 发布:mp4下载软件 编辑:程序博客网 时间:2024/05/22 15:47
1、ThreadLocal相关类图



          ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,(具有线程封闭性),这也是类名中“Local”所要表达的意思。ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。

        ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,通过类图可以看到每个线程都有这样一个类型为ThreadLocal.ThreadLocalMap的threadLocals  map对象,执行 ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的,value就是ThreadLocal具体泛型类型的对象。

        如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

     在 Thread类中, 每个线程都各自关联了一个 ThreadLocal.ThreadLocalMap的类型对象,ThreadLocalMap是一个Map实现类,内部引用了Entry table数组,放置key为ThreadLocal对象,value为具体的T对应的对象 的Entry。

2、ThreadLocal构造实现以及重要方法

 1) 构造器

//ThreadLocal无参数构造器,并没有什么特别之处

  public ThreadLocal() {
    }

 2)Set(value: T): void 设置ThreadLocal类变量方法
  // 可以通过该方法实现为每个线程提供不同的变量拷贝,在方法中首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。
  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
        线程隔离的秘密,就在于ThreadLocalMap这个类。正如类图中看到的,ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(就像java集合中Map对象那样),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象。


3)get(): T 获取ThreadLocal设置的对象的方法
//与void set(T value)方法相呼应,在get()方法首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后以当前this指向的ThreadLocal对象为键进行查找,获取对应的value,若获取到,则返回,否则,调用setInitialValue()。在setInitialValue()方法中,首先调用 T initialValue() 方法,获取value,然后再将value设置到ThreadLocalMap ,过程与Set(T value)类似。注意: initialValue() 访问控制protected ,,所以我们可以覆写该方法,实现对应对象的创建和设置。
   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();
    }

 private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

protected T initialValue() {
        return null;
    }

3、ThreadLocal应用案例
      ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
      该案例是较常见的获取数据库连接问题,因为Connection是非线程安全的,通过使用ThreadLocal为不同线程提供 独立的线程副本,解决多线程获取、使用Connection的并发问题。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

 private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
  @Override
  protected Connection initialValue() {
   Connection conn = null;
   try {
    conn = DriverManager.getConnection(
      "jdbc:mysql://localhost:3306/test", "username",
      "password");
   } catch (SQLException e) {
    e.printStackTrace();
   }
   return conn;
  }
 };

 public static Connection getConnection() {
  return connectionHolder.get();
 }

 public static void setConnection(Connection conn) {
  connectionHolder.set(conn);
 }
}




0 0
原创粉丝点击