# 5.1安全发布对象

## 名称解释

* 发布对象：使一个对象可以被当前范围之外的代码所使用。
* 对象溢出：一种错误的发布。当一个对象还没有构造完成时，就使他被其他线程所见

## 代码演示

### 错误的发布对象示例

```java
package com.moluo.concurrency.publish;

import java.util.Arrays;

public class UnsafePublishExample {
    private String[] states = {"a", "b", "c"}; // 使用private关键字修饰states变量，意图该变量无法被修改

    public String[] getStates() {
        return states;
    }

    public static void main(String[] args) {
        UnsafePublishExample unsafePublishExample = new UnsafePublishExample();
        System.out.println(Arrays.toString(unsafePublishExample.getStates()));

        unsafePublishExample.getStates()[0] = "d"; // 但仍然被修改
        System.out.println(Arrays.toString(unsafePublishExample.getStates()));
    }
}
```

### 对象溢出示例

```java
package com.moluo.concurrency.publish;

public class EscapeExample {

    private int thisCanBeEscape = 0;

    public EscapeExample() {
        new InnerClass(); // 类EscapeExample的初始化需要InnerClass初始化完成
    }

    public static void main(String[] args) {
        new EscapeExample();
    }

    private class InnerClass {
        public InnerClass() {
            System.out.println(EscapeExample.this.thisCanBeEscape); //内部类中使用了EscapeExample.this，即EscapeExample的对象。可这是类EscapeExample还未初始化完成
        }
    }
}
```

## 安全发布对象的四种方法

* 在`静态初始化函数中初始化`一个对象引用
* 将对象的引用保存到`volatile类型域`或者`AtomicReference对象中`
* 将对象的引用保存到某个`正确构造函数对象的final类型域`中
* 将对象的引用保存到一个由`锁保护的域中`

下面我们通过单例来演示安全发布对象的四种方法

### 在`静态初始化函数中初始化`一个对象引用

```java
package com.moluo.concurrency.singleton;

import com.moluo.concurrency.annotation.ThreadSafe;

/**
 * 饿汉模式
 * 单例实例在类装载时进行创建
 *
 * @author moluo
 * @since 2020/6/28
 */
@ThreadSafe
public class SingletonExample2 {

    // 私有的构造函数
    private SingletonExample2() {
    }

    // 单例对象
    private static SingletonExample2 instance = new SingletonExample2();

    // 静态的工厂方法
    public static SingletonExample2 getInstance() {
        return instance;
    }
}
```

```java
package com.moluo.concurrency.singleton;

import com.moluo.concurrency.annotation.ThreadSafe;

/**
 * 懒汉模式
 * 单例实例在第一次使用时进行创建
 *
 * @author moluo
 * @since 2020/6/28
 */
@ThreadSafe
public class SingletonExample5 {

    // 私有的构造函数
    private SingletonExample5() {
    }

    /**
     * 当静态代码块在静态属性之前时，发现getinstance()=null，什么原因造成的呢？
     * 静态代码块先执行，生成一个SingletonExample5对象
     * 静态属性后执行，将instance=null
     * 大写的尴尬！！！
     *
     * 如何修正呢？
     * 按照如下顺序书写代码，写静态属性，再静态块
     *     private static volatile SingletonExample5 instance = null;
     *
     *    static {
     *         instance = new SingletonExample5();
     *     }
     *
     */
    static {
        instance = new SingletonExample5();
    }

    // 单例对象
    private static volatile SingletonExample5 instance = null;

    // 静态的工厂方法
    public static SingletonExample5 getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        SingletonExample5 instance = SingletonExample5.getInstance();
        System.out.println(instance);
    }

}
```

### 将对象的引用保存到`volatile类型域`或者`AtomicReference对象中`

* 将对象的引用保存到一个由`锁保护的域中`

```java
package com.moluo.concurrency.singleton;

import com.moluo.concurrency.annotation.ThreadSafe;

/**
 * 懒汉模式 双重同步锁单例模式
 * 单例实例在第一次使用时进行创建
 *
 * @author moluo
 * @since 2020/6/28
 */
@ThreadSafe
public class SingletonExample4 {

    // 私有的构造函数
    private SingletonExample4() {
    }

    /**
     * 单例对象
     * <p>
     * 使用volatile的原因：防止指令重排序
     * 
     * 我们已知,java初始化一个对象，需要以下三个步骤：
     * 1、memory = allocate() 分配对象的内存空间
     * 2、ctorInstance 初始化对象
     * 3、instance = memory 设置instance指向刚分配的内存
     * <p>
     * 当JVM和cpu优化，可能发生如下指令重排：
     * 1、memory = allocate() 分配对象的内存空间
     * 3、instance = memory 设置instance指向刚分配的内存
     * 2、ctorInstance 初始化对象
     * 
     * 而这将造成
     * 多线程情况下，假设有线程A、B，由于指令重排，当A执行完1、3两个步骤而未执行2时，对象还未初始化成功，
     * 若此时线程B来获取对象，由于步骤3已经执行，线程B是可以获得对象引用的，但此刻对象未初始化完成，故该时刻线程B调用对象方法就会出现问题
     */
    private static volatile SingletonExample4 instance = null;

    // 静态的工厂方法
    public static SingletonExample4 getInstance() {
        if (instance == null) { // 双重检测机制
            synchronized (SingletonExample4.class) { // 同步锁
                if (instance == null) { // 双重检测机制
                    instance = new SingletonExample4();
                }
            }
        }
        return instance;
    }
}
```

### 将对象的引用保存到一个由`锁保护的域中`

```java
package com.moluo.concurrency.singleton;

import com.moluo.concurrency.annotation.NotRecommend;
import com.moluo.concurrency.annotation.ThreadSafe;

/**
 * 懒汉模式
 * 单例实例在第一次使用时进行创建
 *
 * @author moluo
 * @since 2020/6/28
 */
@ThreadSafe
@NotRecommend
public class SingletonExample3 {

    // 私有的构造函数
    private SingletonExample3() {
    }

    // 单例对象
    private static volatile SingletonExample3 instance = null;

    // 静态的工厂方法
    public static synchronized SingletonExample3 getInstance() {
        if (instance == null) {
            instance = new SingletonExample3();
        }
        return instance;
    }
}
```

### 将对象的引用保存到某个`正确构造函数对象的final类型域`中

```java
package com.moluo.concurrency.singleton;

import com.moluo.concurrency.annotation.NotThreadSafe;
import com.moluo.concurrency.annotation.Recommend;
import com.moluo.concurrency.annotation.ThreadSafe;

/**
 * 懒汉模式
 * 单例实例在第一次使用时进行创建
 *
 * @author moluo
 * @since 2020/6/28
 */
@ThreadSafe
@Recommend
public class SingletonExample6 {

    // 私有的构造函数
    private SingletonExample6() {

    }

    // 单例对象
    private static SingletonExample6 instance = null;

    // 静态的工厂方法
    public static SingletonExample6 getInstance() {
        return Singleton.INSTANCE.getSingleton();
    }

    private enum Singleton {
        INSTANCE;

        private SingletonExample6 singleton;

        // JVM保证枚举类的构造方法绝对只调用一次
        Singleton() {
            singleton = new SingletonExample6();
        }

        public SingletonExample6 getSingleton() {
            return singleton;
        }
    }
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://moluo.gitbook.io/notes/bian-cheng-xue-xi/java/java-gao-bing-fa/5.1-an-quan-fa-bu-dui-xiang.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
