发布和逸出:
“publish”,发布一个对象的意思是:使对象能够在当前作用域之外的代码中使用。(Publishingan object means making it available to code outside of its current scope )。发布内部状态可能破坏封装性,并使得程序难以维系不变性条件。如果对象在构造完成前就发布该对象,就会破坏线程安全性。当某个不应该发布的对象被发布时,被称为“逸出”。
public static Set<Secret> know;
public void init() {
know = new HashSet<>();
}
当发布某个对象时,可能会间接的发布其他对象。如果将一个Secret对象添加到集合know中,那么同样会发布这个对象,因为任何代码都可以便利这个集合,并获得对这个心Secret对象的引用。同样从非私有方法返回一个引用,那么同样会发布返回的对象。
private String[] states = new String[] { "AK", "AL" };
public String[] getState() {
return states;
}
代码发布了一个本是私有的状态数组,逸出了他所在的作用域,任何一个调用着都能修改这个数组的内容。封装的主要原因是:封装能够对程序的正确性进行分析,并使无意破坏设计约束条件能难。(
it makesit practical to analyze programs for correctness and harder to violate design con-straints accidentally. )
还有一种发布对象或其内部状态的机制就是发布一个内部的类实例。看例子
</pre><div><span style="white-space: pre;"> </span><pre name="code" class="java">public class ThisEscape {
private String name = null;
public ThisEscape(EventSource eventSource) {
eventSource.registerListener(new EventListener() {
@Override
public void onEvent() {
System.out.println(name.toString());
}
});
name = "23";
}
}
如果想在构造函数中注册一个事件监听或者启动线程,可以使用
一个私有的构造函数,和
一个公共的工厂方法。
看书中的例子:
public class SafeListener {
private final EventListener eventListener;
<span style="color:#ff0000">private</span> SafeListener(EventSource eventSource) {
eventListener = new EventListener() {
@Override
public void onEvent() {
}
};
}
public static SafeListener newInstance(EventSource eventSource) {
SafeListener listener = new SafeListener(eventSource);
eventSource.registerListener(listener.eventListener);
return listener;
}
}
看到newInstance 突然想想到两点 :
1.以前做过一个android 项目,引入的一个第三方框架,好像也是这样写的,当时只知道用,不懂为什么。有时间一定补上
2.newInstance 让我想起了 反射 ,赶紧翻看了下Class 源码,并分析。
<span style="color:#ff0000">private</span> Class() {}
同样,不让自己创建对象,并且带有说明
/*
* Constructor. Only the Java Virtual Machine creates Class
* objects.
*/
只有虚拟机才能创建Class对象,Class 类中static方法不是很多,我们要得到一个字节码只能通过Class.forName,这是不是也是为了防止逸出呢? 请大牛指教。public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName0(className, true,
ClassLoader.getClassLoader(Reflection.getCallerClass()));
}
线程封闭
栈封闭
ThreadLocal类
ThreadLocal类保存的是 线程的标示和要保存的值。查看ThreadLocal源码就能明白原理。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法首先得到当前线程,然后通过传入的对象,取得Map集合,如果map为空就创建,不为空就更新。
同样的 get方法 我们也大约能猜到是什么样的,请看
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
在现实应用程序框架大量的运用了ThreadLocal ,我觉得浏览器在访问服务器时候就可以通过 ThreadLocal来区分客户端。因为 只要浏览器访问服务器 就会开启一个线程。
不可变性:
当一个对象不可变,那他一定是线程安全的。
不可变对象必须包括三点:
1.对象创建后其状态就不能改变。
2.对象的所有域都是fnal类型。
3.对象是正确的创建的。(在对象创建时期,this引用没有逸出)
final关键字修饰的变量 引用不可变,但是对象依然可以被修改。java中,final域能保证初始化过程的安全性。
一个编程的习惯:除非需要更高的可见性,否则应将所有的域都声明为私有域,除非某个域是可见的,否则将他声明为final域。
安全性发布的常用模式:
可变对象必须通过安全方式来发布,这就意味着发布和使用该对象的线程时都必须同步。要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。以下方式可以保证安全的发布:
1.在静态初始化函数中初始化一个对象的引用。
2.将对象的引用保存到volatile类型的域活着
AtomicReference对象中。
3.将对象的引用保存到某个正确构造对象的final类型域中。
4.将对象的引用保存到一个由锁保护的域中。
版权声明:本文为博主原创文章,未经博主允许不得转载。