对象发布和溢出
- 对象发布:使对象能够被当前范围之外的代码所使用。
- 对象溢出:一种错误的发布,使对象还未构造完成就被其他线程看见。
不正确的发布可变对象导致的两种错误:
- 发布线程意外的所有线程都可以看到被发布对象的过期的值
- 线程看到的被发布对象的引用是最新的,然而被发布对象的状态却是过期的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.mmall.concurrency.example.publish; import com.mmall.concurrency.annoations.NotRecommend; import com.mmall.concurrency.annoations.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(); } }
|
这个Escape类的构造函数没有执行完,而他的内部类却对这个类的封装实例进行引用,则已经把这个值打印出来了,而未来这个thisCanBeEscape值仍会改变,比如说在 new 内部类之后对 thisCanBeEscape赋值,那么 log 的值是过期的。这是个 this 引用在构造期间溢出的错误。
安全发布对象的四种方式
- 在静态初始化函数中初始化一个对象引用
- 将对象的应用保存到 volatile 类型的域或者 AtomicReferance 对象中
- 将对象的引用保存到某个正确构造对象的 final 类型域中
- 将对象的引用保存到一个由锁保护的域中。
以上所提到的几种方法都可以应用到单例模式中。
懒汉式单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
@ThreadSafe public class SingletonExample4 {
private SingletonExample4() {
}
private volatile static SingletonExample4 instance = null;
public static SingletonExample4 getInstance() { if (instance == null) { synchronized (SingletonExample4.class) { if (instance == null) { instance = new SingletonExample4(); } } } return instance; } }
|
饿汉模式单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
@ThreadSafe public class SingletonExample2 {
private SingletonExample2() {
}
private static SingletonExample2 instance = new SingletonExample2();
public static SingletonExample2 getInstance() { return instance; } }
|
静态内部类单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class SingletonExample8 {
private SingletonExample8() { }
public static SingletonExample8 getInstance() { return LazyHolder.INSTANCE; }
private static class LazyHolder { private static final SingletonExample8 INSTANCE = new SingletonExample8(); } }
|
枚举模式
上面实现的单例模式不是完全安全的,我们都可以通过反射机制去获取私有构造器更改其访问级别从而实例化多个不同的对象。
这时我们就需要使用到内部枚举类了,因为JVM可以阻止反射获取枚举类的私有构造方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
@ThreadSafe @Recommend public class SingletonExample7 {
private SingletonExample7() {
}
public static SingletonExample7 getInstance() { return Singleton.INSTANCE.getInstance(); }
private enum Singleton { INSTANCE;
private SingletonExample7 singleton;
Singleton() { singleton = new SingletonExample7(); }
public SingletonExample7 getInstance() { return singleton; } } }
|