对象发布: 对象能被其作用域(所在的class)之外的线程访问
对象逸出: 一种错误发布, 对象还没构造完成, 就被其他线程所见
static
JVM保证, 在我们用之前 类一定初始化了,
因此 static field 被读到的一定是初始值(而不是各类型默认0值)
static field 可以在没有加同步机制的情况下, 保证线程能读到 其 初始值(而不是各类型的0值)
保证初始化安全
public class StaticVisibilityExample {
private static Map<String, String> taskConfig;
static {
// 类初始化 这里初始化static field 的taskConfig
Debug.info("The class being initialized...");
taskConfig = new HashMap<String, String>();// 语句①
taskConfig.put("url", "https://github.com/Viscent");// 语句②
taskConfig.put("timeout", "1000");// 语句③
}
public static void init() {
// 该线程至少能够看到语句①~语句③的操作结果,而能否看到语句④~语句⑥的操作结果是没有保障的。
Thread t = new Thread(() -> {
String url = taskConfig.get("url");
String timeout = taskConfig.get("timeout");
doTask(url, Integer.valueOf(timeout));
});
t.start();
}
public static void changeConfig(String url, int timeout) {
taskConfig = new HashMap<>();// 语句④
taskConfig.put("url", url);// 语句⑤
taskConfig.put("timeout", String.valueOf(timeout));// 语句⑥
}
}
这里init
方法会去读类初始化时初始化的static field,
JVM会保证读到的是 static{}
初始化结束的数据
changeConfig
会去写static field,
如果调init
同时 有其他线程调了 changeConfig
那么读到的是 初始化值, 还是更新过的值 就不一定了
类初始化的延迟加载
实际上 类初始化 常用了延迟加载
类被Jvm加载以后, 所有static field 都是其0值(默认值),
直到任意一个static field 被访问, 类才会初始化(就是static{这里被执行})
public class ClassLazyInitDemo {
public static void main(String[] args) {
Debug.info(Collaborator.class.hashCode());// 语句①
Debug.info(Collaborator.number);// 语句②
Debug.info(Collaborator.flag);
}
static class Collaborator {
//2个静态变量
static int number = 1;
static boolean flag = true;
static {
//类初始化就是调用这里
Debug.info("Collaborator initializing...");
}
}
}
语句① 时类就会被加载
语句② 一个static field 就是 number
被访问, 会触发类初始化
类初始化就 是
static {
number = 1;
flag = true;
Debug.info("Collaborator initializing...");
}
final
final static 也能保证 其他线程来读的时候 已经初始化完毕了
保证 有序性
例子:
public class FinalFieldExample {
final int x;
int y;
// 对象instance被发布
static FinalFieldExample instance;
private FinalFieldExample() {
//2个field都在对象构造方法里面 写了 初始化
x = 1;
y = 2;
}
//一个线程写
public static void writer() {
instance = new FinalFieldExample();
}
//另一个线程读
public static void reader() {
final FinalFieldExample theInstance = instance;
if (theInstance != null) {
System.out.println(instance.x);
System.out.println(instance.y);
}
}
}
一个线程 写 调用writer()
, 初始化被发布的instance
对象
另一个线程 读 调用 reader()
writer()
看起来只有一行, 但是不是原子操作, 是多个指令组成的:
1.分配对象的空间
2.对象的x=1
3.对象的y=2
4.把对象引用给instance
因为x
是final修饰的, 因此 步骤 2的后面有内存屏障 保证 4 一定在2的后面, 禁止重排,
但是 步骤3 , 因为y
没有被 final 修饰, 没有禁止重排, 所以4 可能在 3之前,
if (theInstance != null)
成立时, x=1
一定已经执行了 但是 y=2
的完成没有保证