对象发布
- 使一个对象能够被当前范围之外的代码使用;
对象逸出
- 一种错误的发布 ,当一个对象还没有构造完成时,就使它被其他线程所见;
安全发布对象的4种方式
- 在静态初始化函数中初始化一个对象引用;
- 将对象的引用保存到volatile类型域或者AtomicReference对象中;
- 将对象的引用保存到某个正确构造对象的final类型域中;
- 将对象引用保存到一个由锁保护的域中;
不安全发布示例
- UnsafePublish对象的私有域states通过公有getter方法被发布,使其可以被任何线程修改,各线程获取到的state的值是不确定的,这样的发布对象就是线程不安全的;
import com.example.concurrency.annotations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
@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()));
}
}
对象逸出示例
- 在Escape对象还没有构建完成时,其构造器中另开的线程将其this发布出去;
- 如果要在构造器中创建另一个线程,不要启动它,通过专有的start()或初始化方法同一启动线程;可以使用工厂方法和私有构造器完成对象创建和监听器注册;
- 创建可变对象时的目标是:在其构建尚未完成时,不能将其发布;
不正确的发布可变对象将导致2种错误
- 发布线程以外的任何线程都可以看到被发布对象的过期的值;
- 发布线程以外的任何线程看到的引用是最新的,然而被发布对象的状态却是过期的;
结论:如果一个对象是可变对象,其必须被安全发布;
import com.example.concurrency.annotations.NotRecommend;
import com.example.concurrency.annotations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@NotThreadSafe
@NotRecommend
public class Escape {
private int thisCanBeEscape = 0;
public Escape () {
new InnerClass();
}
private class InnerClass {
public InnerClass() {
log.info("{}", Escape.this.thisCanBeEscape);
}
}
public static void main(String[] args) {
new Escape();
}
}
输出:
11:45:00.443 [main] INFO com.example.concurrency.example.publish.Escape - 0