后入式对象的发布与逃逸

何为对象的发布?
大到把项目的war发布到Tomcat上,小到是一个对象能够被当前范围之外的代码访问到

不安全发布unsafe publish

/**
 * 对象的不安全发布
 * 逃逸:对象还没有被构造完成的时候 就被发布(其他线程可见)
 */
public class PublishAndOverflow {
    
    
    //private修饰的成员变量 只能当前类当中使用
    private String[] strArr = {
    
    "a", "b", "c", "d"};

    public final int count;
    public static PublishAndOverflow object;

    //但是这种造成不安全发布
    public String[] getStrArr() {
    
    
        return strArr;
    }
    //构造方法,这种造成逃逸!
    public PublishAndOverflow(){
    
    
        count = 66;
        //这个理解是:在count还没有被初始化之前 object被指令重排程序排在前面先执行
        //而且被其他线程可见-->导致这个构造方法没有被完全执行完 object就被其他线程可见
        object=this;
        //这个this 是在对象实例化之后的引用值new PublishAndOverflow()
    }

    public void setStrArr(String str) {
    
    
        this.strArr[0] = str;
    }

    public static void writer(){
    
    
        new PublishAndOverflow();
    }

    public static void reader(){
    
    
        if (object != null){
    
    
            int temp = object.count;
        }
    }

    public static void main(String[] args) {
    
    
        PublishAndOverflow publishAndOverflow = new PublishAndOverflow();
        publishAndOverflow.setStrArr(publishAndOverflow.getStrArr()[3]);
        System.out.println(Arrays.toString(publishAndOverflow.getStrArr()));
        Arrays.stream(publishAndOverflow.getStrArr()).forEach(System.out::println);
    }
    /**
     * 如何安全的发布一个对象,四种方法
     * 1.在静态初始化函数中初始化一个对象的引用
     * 2.将对象的引用保存到volatile类型的域,或者Atomic对象中
     * 3.将对象的引用保存到一个正确的构造对象的final域当中
     * 4.将对象保存到读写都加上锁的的域中
     */
}

安全的发布对象的四种常用方法

  • 如何安全的发布一个对象,四种方法
    1. 在静态初始化函数中初始化一个对象的引用
    2. 将对象的引用保存到volatile类型的域,或者Atomic对象中
    3. 将对象的引用保存到一个正确的构造对象的final域当中
    4. 将对象保存到读写都加上锁的的域中

先说一下单例模式的好处 以及为什么要私有化构造函数

public class PrivateStructureTest {
    
    
    // 构造方法私有化的话,这个类就无法在其他地方创建对象。
    // 单例模式的好处是:
    //1、提供了对唯一实例的受控访问。
    //2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    //3、允许可变数目的实例。
    private PrivateStructureTest(){
    
    }

    private int age=18;
    private String name = "ranran";

    @Override
    public String toString() {
    
    
        return "PrivateStructureTest{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

在静态初始化函数中初始化一个对象的引用,也就是所谓的“饿汉模式”,单例在装载的时候就被创建出来
如果构造方法存在过多的处理,导致类加载特别慢,性能问题,只加载,不使用,资源浪费
因此 饿汉模式在:私构造函数没有太多处理 单例肯定会被使用的情况下 使用比较好

/**
 * 对象的不安全发布
 * 逃逸:对象还没有被构造完成的时候 就被发布(其他线程可见)
 */
public class SafePublishOfStatic {
    
    
    /**
     * 1.在静态初始化函数中初始化一个对象的引用
     */
    // 单例一定要先私有化构造 使得这个类的对象在其他的无法被实例化
    // Error:(17, 53) java: PrivateStructureTest() 在 threadsecurity.PrivateStructureTest 中是 private 访问控制
    private SafePublishOfStatic(){
    
    }
    // 然后私有化 实例对象 加上static修饰 在jvm层面 会加上一个锁 确保这个类已经被加载之后再赋值
    private static SafePublishOfStatic instance = new SafePublishOfStatic();
    // 然后通过public公开发布实例
    public static SafePublishOfStatic getInstance(){
    
    
        return instance;
    }
}

final域

public class SafePublishOfFinal {
    
    
    // final域可以防止 map的初始化被指令重排序到构造函数之外
    private final Map<String, Object> map;

    public SafePublishOfFinal(){
    
    
        map = new HashMap<>();
        map.put("key", "value");
    }
}

这个是所谓的“懒汉模式”,单例在第一次被使用的时候创建

public class SafePublishOfVolatile {
    
    
    private SafePublishOfVolatile(){
    
    }
    private static SafePublishOfVolatile instance = null;
    // 这个方法明显不是线程安全的 在多线程情况下会导致多个实例
    public static SafePublishOfVolatile getInstance(){
    
    
        if (instance == null) instance = new SafePublishOfVolatile();
        return instance;
    }
}

加synchronized,在高并发情况下 导致性能不够好,如果实际情况单例已经有了但是还是继续加锁,性能比较差

public static synchronized SafePublishOfVolatile getInstance(){
    
    
    if (instance == null) instance = new SafePublishOfVolatile();
    return instance;
}

继续优化,缩小锁的范围给相应的核心代码块加上synchronized

public static SafePublishOfVolatile getInstance(){
    
    
    // 如果已经存在单例 就直接返回 不会继续一直加锁影响性能
    if (instance == null) {
    
    
        synchronized (SafePublishOfVolatile.class) {
    
    
        	// 双重检测机制
            if (instance == null)
            instance = new SafePublishOfVolatile();
        }
    }
    return instance;
}

但是这种情况下还是存在一个CPU或JVM层面的指令重排序问题,因为new操作指令不是原子性的分为三步

    1. memory = allocate() 分配对象的内存空间
    1. ctorInstance(memory) 初始化对象
    1. instance = memory 设置instance指向刚分配的内存
public class SafePublishOfVolatile {
    
    
    private SafePublishOfVolatile(){
    
    }
    // 本质上 volatile 实际上是通过内存屏障来防止指令重排序
    // 以及禁止CPU高速缓存来解决可见性问题
    private volatile static SafePublishOfVolatile instance = null;

    /**
     * 在不影响最终的执行结果 CPU层面会存在指令的重排序以提高 处理器内部运算单元尽可能被充分利用
     * new 操作的JVM层面的指令包括
     * 1. memory = allocate() 分配对象的内存空间
     * 2. ctorInstance(memory) 初始化对象
     * 3. instance = memory 设置instance指向刚分配的内存
     * 2.3步可能被重排序 导致还没来得及初始化对象 就已经暴露instance
     */
    public static SafePublishOfVolatile getInstance(){
    
    
        // 如果已经存在单例 就直接返回 不会继续一直加锁影响性能
        if (instance == null) {
    
    
            synchronized (SafePublishOfVolatile.class) {
    
    
                // 双重检测机制
                if (instance == null)
                instance = new SafePublishOfVolatile();
            }
        }
        return instance;
    }
}

饿汉模式的另一种 加静态代码块

public class SafePublishOfStatic {
    
    
    // 单例一定要先私有化构造
    private SafePublishOfStatic(){
    
    }
    // 然后私有化 实例对象 加上static修饰 在jvm层面 会加上一个锁 确保这个类已经被加载之后再赋值
    private static SafePublishOfStatic instance;
    // 静态代码块 一定要注意顺序
    static {
    
    
        instance = new SafePublishOfStatic();
    }
    // 然后通过public公开发布实例
    public static synchronized SafePublishOfStatic getInstance(){
    
    
        return instance;
    }
}

经此一战,后入式很舒服,而且节省体力,建议实战演练

猜你喜欢

转载自blog.csdn.net/blackxc/article/details/108000581