当访问共享数据时,通常需要使用同步。一种可以避免使用同步的方式就是不共享数据,如果仅在单线程内访问数据就不需要同步。这种技术被称为线程封闭。
1. Ad-hoc线程封闭
Ad-hoc线程封闭指的是,维护线程封闭性的职责完全由程序来承担。Ad-hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,例如可见性修饰符或局部变量,能将对象封闭到目标线程上。事实上,对线程封闭对象(例如,GUI应用程序中的可视化组件或数据模型等)的引用通常保存在公有变量中。
总结出来一句话 就是:由程序员自己写代码实现线程封闭。
2.栈封闭
局部变量的固有属性之一就是封闭在执行线程中,如果只能通过局部变量访问对象就实现了对象的栈封闭。栈封闭也被称为线程内部使用或者线程局部使用。
在下面程序段中 loadTheArk 方法的返回 numPairs 时是无法破坏栈的封闭性的。
import java.util.*;
/**
* Animals
*
* Thread confinement of local primitive and reference variables
*
* @author Brian Goetz and Tim Peierls
*/
public class Animals {
Ark ark;
Species species;
Gender gender;
public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numPairs = 0;
Animal candidate = null;
// animals confined to method, don't let them escape!
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;
}
class Animal {
Species species;
Gender gender;
public boolean isPotentialMate(Animal other) {
return species == other.species && gender != other.gender;
}
}
enum Species {
AARDVARK, BENGAL_TIGER, CARIBOU, DINGO, ELEPHANT, FROG, GNU, HYENA,
IGUANA, JAGUAR, KIWI, LEOPARD, MASTADON, NEWT, OCTOPUS,
PIRANHA, QUETZAL, RHINOCEROS, SALAMANDER, THREE_TOED_SLOTH,
UNICORN, VIPER, WEREWOLF, XANTHUS_HUMMINBIRD, YAK, ZEBRA
}
enum Gender {
MALE, FEMALE
}
class AnimalPair {
private final Animal one, two;
public AnimalPair(Animal one, Animal two) {
this.one = one;
this.two = two;
}
}
class SpeciesGenderComparator implements Comparator<Animal> {
public int compare(Animal one, Animal two) {
int speciesCompare = one.species.compareTo(two.species);
return (speciesCompare != 0)
? speciesCompare
: one.gender.compareTo(two.gender);
}
}
class Ark {
private final Set<AnimalPair> loadedAnimals = new HashSet<AnimalPair>();
public void load(AnimalPair pair) {
loadedAnimals.add(pair);
}
}
}
即使在线程内部上下文中使用非线程安全的对象, 该对象仍然是线程安全的。但是在后续代码维护过程中很容易错误的时该不安全对象逸出,此时线程安全性就会遭到破坏。
3. ThreadLocal 类
维持线程封闭性的一种更规范的方法是使用ThreadLocal,这个类能够使线程中的某个值与保存值得对象关联起来。ThreadLocal 类提供了 get 和set 等访问接口或者方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此 get 总是返回当前执行线程在调用 set 时设置的最新值。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* ConnectionDispenser
*
* Using ThreadLocal to ensure thread confinement
*
* @author Brian Goetz and Tim Peierls
*/
public class ConnectionDispenser {
static String DB_URL = "jdbc:mysql://localhost/mydatabase";
private ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
try {
return DriverManager.getConnection(DB_URL);
} catch (SQLException e) {
throw new RuntimeException("Unable to acquire Connection, e");
}
};
};
public Connection getConnection() {
return connectionHolder.get();
}
}
ThreadLocal常用于防止对可变的单实例变量或全局变量进行共享。在单线程应用程序中可能要维护一个全局的数据库连接,并在程序启动时初始化这个链接对象,从而避免在调用每个方法时都要传递一个Connection对象。
当某个频繁执行的操作需要一个临时对象时,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用该项技术。在实现应用程序框架时大量使用了ThreadLocal 。例如,在EJB 调用期间,J2EE 容器需要将一个事务上下文与某个执行中的线程关联起来。通过将事务上下文保存在静态的ThreadLocal 对象中,就可以实现这个功能:当框架代码需要判断当前运行的是哪一个事务时,只需要从这个ThreadLocal 对象中读取事务上下文。