发布对象
简单来说就是提供一个对象的引用给作用域之外的代码。比如return一个对象,或者作为参数传递到其他类的方法中。
不安全的发布对象示例:
@Slf4j
@NotThreadSafe
public class UnsafePublish {
private String[] states = {"a", "b", "c"};
public String[] getStates() {
return states;
}
public static void main(String[] args) {
UnsafePublish unsafePublish = new UnsafePublish();
log.info("{}", Arrays.toString(unsafePublish.getStates()));
// 发布对象不安全,可被修改
unsafePublish.getStates()[0] = "d";
log.info("{}", Arrays.toString(unsafePublish.getStates()));
}
}
对象逸出
如果一个类还没有构造结束就已经提供给了外部代码一个对象引用即发布了该对象,此时叫做对象逸出,对象的逸出会破坏线程的安全性。
public class Escape {
private int thisCanBeEscape = 1;
public Escape() {
new InnerClass();
// 还有业务需要执行
thisCanBeEscape++;
}
private class InnerClass {
public InnerClass() {
log.info("{}", Escape.this.thisCanBeEscape);
}
}
public static void main(String[] args) {
new Escape();
}
}
- 这个内部类的实例里面包含了对封装实例的私有域对象的引用,在对象没有被正确构造完成之前就会被发布,有可能有不安全的因素在里面,会导致this引用在构造期间溢出的错误。
- 上述代码在函数构造过程中启动了一个线程。无论是隐式的启动还是显式的启动,都会造成这个this引用的溢出。新线程总会在所属对象构造完毕之前就已经看到它了。
类名.this的解释说明
“类名.this”的语法在Java语言中叫做“qualified this”。 这个语法的主要用途是:在内部类的方法中,要指定某个嵌套层次的外围类的“this”引用时,使用“外围类名.this”语法。例如说:
class Foo {
class Bar {
Foo getFoo() {
return Foo.this;
}
}
}
在Foo.Bar类中的getFoo()方法中,如果直接写“this”的话所指的是这个Foo.Bar类的实例,而如果要指定外围的Foo类的this实例的话,这里就得写成Foo.this。
特别的,如果在上例的getFoo()方法中写Bar.this的话,作用就跟直接写this一样,指定的是当前的Foo.Bar类实例。
安全发布对象
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
可以联想下单例模式中饿汉模式/懒汉模式。
安全共享对象策略
- 线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改。
- 共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。
- 线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它。
- 被守护对象:被守护对象只能通过获取特定的锁来访问。
网友评论