线程封闭:当访问共享的可变数据时,通常需要同步。一种避免同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭(thread  confinement)

  线程封闭技术一个常见的应用就是JDBC的Connection对象,JDBC规范并没有要求Connection对象必须是线程安全的,在服务器应用程序中,线程从连接池获取一个Connection对象,使用完之后将对象返还给连接池。下面介绍几种线程封闭技术:

  1、Ad-hoc线程封闭

  Ad-hoc线程封闭是指,维护线程的封闭性的职责完全由程序实现承担,是非常脆弱的,因此在程序中尽量少使用,一般使用更强的线程封闭技术,比如栈封闭或者ThreadLocal类。

2、栈封闭  

  栈封闭是线程封闭的一种特列,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行栈中,其他线程无法访问这个栈,栈封闭也称为线程内部使用或者线程局部使用。简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

  比如下面的例子:

 public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numPairs = 0;
Animal candidate = null; //animals被封装在方法中,不要使它们溢出
animals = new TreeSet<Animal>(new SpeciesGenderComparator());
animals.addAll(candidates);
for(Animal a:animals){
if(candidate==null || !candidate.isPotentialMate(a)){
candidate = a;
}else{
ark.load(new AnimalPair(candidate,a));
++numPairs;
candidate = null;
}
}
return numPairs;

  在loadTheArk中实例化一个TreeSet对象,并将该对象的一个引用保存到animals中。此时,只有一个引用指向集合animals,这个引用被封闭到局部变量中,因此也被封闭到局部变量中。然而,如果发布了对集合animals(或者该对象中的任何内部数据)的引用,那么封闭性将被破坏,并导致对象animals的逸出。

3、ThreadLocal类

  维持线程封闭性的一种更加规范方法是使用ThreadLocal类,这个类能使线程中某个值与保存值的对象关联起来。ThreadLocal类提供了get和set等访问接口或者方法,这些方法为每个使用该变量的线程都存在一份独立的副本,因此get总是放回当前执行线程在调用set设置的最新值。看一下下面代码例子:

 public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public 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);
}
}

  通过调用ConnectionManager.getConnection()方法,每个线程获取到的,都是自己独立拥有的一个的Connection对象副本,第一次获取时,是通过initialValue()方法的返回值来设置值的。通过ConnectionManager.setConnection(Connection conn)方法设置的Connection对象,也只会和当前线程绑定。这样就实现了Connection对象在多个线程中的完全隔离。在Spring容器中管理多线程环境下的Connection对象时,采用的思路和以上代码非常相似。

  每个线程是怎么和Connection对象副本绑定的?这个对象副本保存在哪里。当某个线程初次调用ThreadLocal类的get方法时,就会调用initialValue来获取初始值,从概念上看,我们可以将ThreadLocal<T>视为包含了Map<thread, T>对象,其中保存了特定于该线程的值,但是ThreadLocal的实现并非如此,这样只是为了我们方便理解而已。

  下面我们来分析一下ThreadLocal的源码。ThreadLocal类的方法很简单,只有四个,分别为set,get,remove, initialValue,从字面上我们也能理解这些方法的作用。

  public T get():返回当前线程所对应的局部变量。

  public void set(T arg0):设置当前线程局部变量的值。  

  public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。注意,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

  protected T initialValue(): 对当线程局部变量进行初始化,并返回该初始值。是protected 属性,显然是让子类进行对其覆盖重写的,只有第一次调用set和get方法时才调用。  

  下面我们对这四个方法的源码进行分析,看看ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”。

3.1 set方法

  以下是set方法的源码

 public void set(T arg0) {
Thread arg1 = Thread.currentThread();
ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);
if (arg2 != null) {
arg2.set(this, arg0);
} else {
this.createMap(arg1, arg0);
} }

  从set方法中可以看到,首先获取当前线程:Thread arg1 = Thread.currentThread();

  再获取当前线程的ThreadLocalMap:ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);

  判断ThreadLocalMap是否为空,不为空,则以键值对的形式设置值,key为this,value就是局部变量的副本,this是当前线程持有的ThreadLocal类实例化对象。

  假如为空,则通过createMap方法创建。

  我们看下getMap和createMap方法的源码:

 ThreadLocal.ThreadLocalMap getMap(Thread arg0) {
return arg0.threadLocals;
} void createMap(Thread arg0, T arg1) {
arg0.threadLocals = new ThreadLocal.ThreadLocalMap(this, arg1); }

  从代码上已经写的非常清楚,每个线程都有自己的局部变量的副本,该副本是存在ThreadLocalMap 中,其中键值就是ThreadLocal类实例化对象。也就是说每个线程都拥有自己的ThreadLocalMap,ThreadLocalMap保存的就是局部变量副本。我们看一下java.lang.Thread源码。

 private static int threadInitNumber;
ThreadLocalMap threadLocals = null;
ThreadLocalMap inheritableThreadLocals = null;

3.2 get方法

 public T get() {
Thread arg0 = Thread.currentThread();
ThreadLocal.ThreadLocalMap arg1 = this.getMap(arg0);
if (arg1 != null) {
ThreadLocal.ThreadLocalMap.Entry arg2 = arg1.getEntry(this);
if (arg2 != null) {
Object arg3 = arg2.value;
return arg3;
}
} return this.setInitialValue();
}

  从代码上看,前两步和set方法是一个样的,分别获取当前线程和当前线程的ThreadLocalMap,第三步判断ThreadLocalMap是否为空,不为空根据this键值获取value,为空调用setInitialValue()方法。

  以下是setInitialValue方法代码:

 private T setInitialValue() {
Object arg0 = this.initialValue();
Thread arg1 = Thread.currentThread();
ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);
if (arg2 != null) {
arg2.set(this, arg0);
} else {
this.createMap(arg1, arg0);
} return arg0;
}

  在setInitialValue里调用了initialValue()方法,也就是子类要重写覆盖的方法,对应上面的例子的代码是:

 protected Connection initialValue() {
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}

  然后获取当前线程和当前线程的ThreadLocalMap,ThreadLocalMap为空则调用createMap,否则调用set方法。

3.3 总结

  ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。

  进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。

  也就说,每个线程都有一个ThreadLocalMap,该线程访问到某个局部变量,且该局部变量是用ThreadLocal类进行声明时,该线程就会new ThreadLocal(),然后将该ThreadLocal类的对象作为key值,所对应的局部变量作为value值保存到ThreadLocalMap中。当线程访问多个ThreadLocal类进行声明局部变量时,在ThreadLocalMap中就有多个键值对。而每个线程都有自己的ThreadLocalMap,从而达到隔离的目的了。

  当某个线程终止后,该线程里的ThreadLocalMap也被回收了,所以完全不用担心内存泄漏的问题。

  假如多线程访问的对象实例是单例的,或者说只能创建一个,那就老老实实的使用同步机制(synchronized)了.

最新文章

  1. 代码管理工具 --- git的学习笔记一《git的个人开发》
  2. ReactJS入门(一)—— 初步认识React
  3. type=&quot;file&quot; 选择图片后预览
  4. [转]C#编程总结(三)线程同步
  5. UITableView 小节-备
  6. Java之File类
  7. VAssistX插件
  8. 清空dataset中的某行某列的数据
  9. Oracle 查询表对应的索引
  10. JarvisOJ Basic Help!!
  11. VMware 无法打开内核设备 \\.\Global\vmx86
  12. OGG选择捕捉和应用模式
  13. mongodb 使用mongodump备份 指定用户名密码 出现错误 Failed: error connecting to db server: server returned error on SASL authentication step: Authentication failed
  14. web安全基础
  15. PLSQL Developer 连接Linux 下Oracle的安装与配置
  16. 【配置】log4j.properties 详解与配置步骤
  17. spark pyspark 常用算法实现
  18. import static和import的区别(转)
  19. 有用sql语句一
  20. Android 将APK文件安装到AVD中并分析其界面结构

热门文章

  1. Docs命令大全 备用
  2. 《OpenGL超级宝典》编程环境配置
  3. DB2存储过程通过游标实现批量数据处理
  4. 斜杠反斜杠,去空格\xa0,连接函数join()
  5. JavaScript 语法总结
  6. LVS初步
  7. oracle树形语句
  8. Git管理
  9. Thread in depth 1: The basic
  10. Postgresql fillfactor