本章介绍如何共享和发布对象,从而使它们能够安全的由多个线程同时访问。
定义 。发布:发布一个对象是指使对象能够在当前作用域之外的代码中使用(将一个指向对象的引用保存到其他代码可以访问的地方;在某一个非私有方法中返回该引用;将引用传递到其他类的方法中)。逸出:不该发布的对象被发布时(不想被发布的内部状态(私有,发布会破坏封装性);对象在构造完成前就被发布)。
发布对象的方式 :
1.将对象的引用保存到一个公有的静态变量中
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}
2,从非私有方法中返回一个引用
class UnsafeStates {
private String[] states = new String[]{
"AK", "AL" /*...*/
};//私有可变状态
public String[] getStates() {
return states;
}
}
当发布一个对象时,在该对象非私有域中引用的所有对象同样会被发布(逸出)。
3,构造过程中发布一个内部类实例
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
当ThisEscape发布EventListener(或者启动一个线程,那么this引用在未构造完成之前就可以被新线程看见)时,也隐含的发布了ThisEscape本身,因为在这个内部类的实例中包含了对ThisEscape实例的隐含引用。因此当对象的构造函数中发布对象时,发布了一个尚未构造完成的对象(逸出)。
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
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;
}
}
public class ConnectionDispenser {
static String DB_URL = "jdbc:mysql://localhost/mydatabase";
private ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
//当某个线程初次调用ThreadLocal.get方法时,就会调用initialValue来获取初始值
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();
}
}
通过将JDBC的连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的连接。
public class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
public OneValueCache(BigInteger i,
BigInteger[] factors) {
lastNumber = i;
//必须用copyof或者clone来初始化lastFactors(不懂)
lastFactors = Arrays.copyOf(factors, factors.length);
}
public BigInteger[] getFactors(BigInteger i) {
if (lastNumber == null || !lastNumber.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
@ThreadSafe
public class VolatileCachedFactorizer extends GenericServlet implements Servlet {
private volatile OneValueCache cache = new OneValueCache(null, null);
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(resp, factors);
}
}
public class StuffIntoPublic {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
}
由于可见性问题,其他线程看到的Holder对象将处于不一致的状态,即便在该对象的构造函数中已经正确的构建了不变性条件。这种不正确的发布导致其他线程看到尚未创建完成的对象。
public class StuffIntoPublic {
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
public void assertSanity(){
if(n!=n) throw new AssertionError("this statment is false");
}
由于没有使用同步来确保Holderd对象对其他线程可见,因此这是不正确的发布。一个线程在调用assertSanity方法时可能会抛出异常。
1.在静态初始化函数中初始化一个对象引用
2.将对象的引用保存到volatile类型的域。
3.将对象的引用保存到某个正确构造的final类型域中。
4.将对象的引用保存到一个由锁保护的域中。