发布对象:使一个对象能够被当前范围之外的代码所使用。
对象逸出:一种错误的发布。当一个对象还没有构造完成,就使它被其他线程所见。
发布对象的线程不安全示例:
public class UnsafePublish {
private String[] states = {"a","b","c"};
public String[] getStates(){
return states;
}
public static void main(String[] args) {
UnsafePublish unsafePublish = new UnsafePublish();
System.out.println(Arrays.toString(unsafePublish.getStates()));
unsafePublish.getStates()[0] = "d";
System.out.println(Arrays.toString(unsafePublish.getStates()));
}
}
这样发布的对象是线程不安全的,因为每个线程都可以通过调用对象实例的方法修改对象中的私有变量,因此在使用该实例的私有变量时产生的结果是不确定的。
对象逸出:
public class Escape {
private int thisCanBeEscape = 0;
public Escape() {
new inner();
}
private class inner{
public inner() {
System.out.println(Escape.this.thisCanBeEscape);
}
}
public static void main(String[] args) {
new Escape();
}
}
内部类中包含对封装实例的隐含和引用,这样可能导致对象没有被正确构造完成前就被发布,可能造成线程不安全的问题。(一般使用工厂方法,私有构造函数解决)
安全发布对象
- 在静态初始化函数中初始化一个对象。
- 将对象的引用保存到volatile类型域或者AtomicReference对象中。
- 将对象的引用保存到某个正确构造对象的final类型域中。
- 将对象的引用保存到一个由锁保护的域中。
双检查单例模式的演变:
- 懒汉模式(线程不安全)
public class SingletonExample1{
//私有构造函数
private SingletonExample1() {
}
//单例对象
private static SingletonExample1 instance = null;
//静态工厂方法
public static SingletonExample1 getInstance(){
if (instance == null) {
instance = new SingletonExample1();
}
return instance;
}
}
- 懒汉模式(线程安全,并发效率低)
public class SingletonExample2{
//私有构造函数
private SingletonExample2() {
}
//单例对象
private static SingletonExample2 instance = null;
//静态工厂方法
public static synchronized SingletonExample2 getInstance(){
if (instance == null) {
instance = new SingletonExample2();
}
return instance;
}
}
- 双检查单例模式(小概率线程不安全)
public static class SingletonExample3{
//私有构造函数
private SingletonExample3() {
}
//单例对象
private static SingletonExample3 instance = null;
//静态工厂方法
public static SingletonExample3 getInstance(){
if (instance == null) {
synchronized (SingletonExample3.class){
if (instance == null) {
instance = new SingletonExample3();
}
}
}
return instance;
}
}
线程不安全原因:
正常对象实例创建流程:
1、memory = allocate() 分配对象内存空间
2、ctorInstance()初始化对象
3、instance = memory 设置instance指向刚分配的内存
jvm允许指令重排优化
导致实际执行流程:
1、memory = allocate() 分配对象内存空间
3、instance = memory 设置instance指向刚分配的内存
2、ctorInstance()初始化对象
小概率会导致线程获得的实例没有被初始化,出现线程安全问题。
使用volatile修饰来解决
- 双检查单例模式(线程安全)
public static class SingletonExample4{
//私有构造函数
private SingletonExample4() {
}
//单例对象
private static volatile SingletonExample4 instance = null;
//静态工厂方法
public static SingletonExample4 getInstance(){
if (instance == null) {
synchronized (SingletonExample4.class){
if (instance == null) {
instance = new SingletonExample4();
}
}
}
return instance;
}
}
最安全的单例:
枚举实现单例:
public class SingletonEnum {
private SingletonEnum() {
}
public static SingletonEnum getInstance() {
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private SingletonEnum singleton;
//JVM保证构造方法只被调用一次
Singleton() {
singleton = new SingletonEnum();
}
public SingletonEnum getInstance() {
return singleton;
}
}
}