并发编程与高并发学习笔记二

发布安全对象
一,发布对象
1.发布对象:是一个对象能够被当前范围之外的代码所使用
2.对象逸出:一种错误的发布。当一个对象还没有构造完成时,就使他被其他线程所见
//发布对象,这是一个不安全的对象
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(unsafePublish.getStates());

        //这里我修改了数组里的一个值
        //所以这是一个不安全的对象,我们无法保证另外一个线程会不会修改对象里的属性的值
        unsafePublish.getStates()[0] = "d";
        System.out.println(unsafePublish.getStates());
    }
}

//对象逸出,(在一个对象未完成创建之前,不可以将其发布)
public class Escape {
    private int thisCanBeEscape = 0;

    public Escape(){
        new InnerClass();
    }

    /**
     *      内部类的实例里面包含了对包装实例的引用,在对象没有被构造完成之前,就会被发布。
     * 有可能存在不安全的因素。一个导致this引用在构造期间溢出的错误,他是在构造函数中
     * 相当于启动了一个新的线程,无论是隐式的启动还是显示的启动,都会造成this引用的逸出。
     * 新线程会在所属对象构造完毕之前就已经看到它(this)了,
     *      所以要在构造函数中创建线程,那么不要启动它。而是采用一个专有的start,或初始化的方法,
     * 来统一启动线程。可以采用工厂方法和私有构造函数来完成对象的创建和监听器的注册等等
     * 这样才可以避免不正确的创建
     *
     *
     */
    private class InnerClass{
        public InnerClass(){
            System.out.println(Escape.this.thisCanBeEscape);
        }
    }

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

,安全的发布对象
1.在静态初始化函数中初始化一个对象的引用
2.将对象的引用保存到volatile类型域或者AtomicReference对象中
3.将对象的引用保存到某个正确构造对象的final类型域中
4.将对象的引用保存到一个由锁保护的域中
三,创建单例的方法
1.饿汉模式
/**
 * 饿汉模式
 * 单例实例在类装载时创建
 */
//线程安全的
/*
    缺点:如果构造函数中存在过多的处理,会导致类加载时非常慢,因此会引起性能问题
         如果加载类后并没有调用,会造成资源的浪费
 */
public class SingletonHungry {
    //私有构造函数
    private SingletonHungry(){
    }
    //单例对象
    private static SingletonHungry instance = new SingletonHungry();
    //静态工厂方法
    public static SingletonHungry getInstance(){
        return instance;
    }
}

2.饿汉模式优化

/**
 * 饿汉模式
 * 单例实例在类装载时创建
 */
//线程安全的
/*
    缺点:如果构造函数中存在过多的处理,会导致类加载时非常慢,因此会引起性能问题
         如果加载类后并没有调用,会造成资源的浪费
 */
public class SingletonHungry2 {
    //私有构造函数
    private SingletonHungry2(){
    }
    //单例对象
    private static SingletonHungry2 instance = null;
    /*
        使用静态块来初始化对象,
        注意:private static SingletonHungry2 instance = null; 一定要在前面
     */
    static {
        instance = new SingletonHungry2();
    }
    //静态工厂方法
    public static SingletonHungry2 getInstance(){
        return instance;
    }
}

3.懒汉模式1

/**
 * 懒汉模式
 * 单例实例在第一次使用时创建
 */
//线程不安全的
public class SingletonLazy {
    //私有化构造函数
    private SingletonLazy(){
    }
    //定义单例对象
    public static SingletonLazy instance = null;
    //静态工厂方法
    public static SingletonLazy getInstance(){
        /**
         * 单线程中这种方法没问题,
         * 但是在多线程中,当两个线程同时判断instance == null时,会创建两个不同的实例
         * 如果我们在构造方法中什么也不做,就算两个对象不同,似乎也不会出什么问题啊
         * 但是如果我们在构造方法中做一些运算的操作,那么创建两次对象,运算的结果就会出错
         *
         */
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
}

4.懒汉模式2

/**
 * 懒汉模式
 * 单例实例在第一次使用时创建
 */
//线程安全的,但是不推荐这种方法
public class SingletonLazy2 {
    //私有化构造函数
    private SingletonLazy2(){
    }
    //定义单例对象
    private static SingletonLazy2 instance = null;
    //静态工厂方法
    /*
        添加了synchronized后,该方法在同一时间只允许一个线程访问,可以保证线程安全.
        但是不推荐这种方法,这种方法带来了性能上的开销
     */
    public static synchronized SingletonLazy2 getInstance(){
        /**
         * 单线程中这种方法没问题,
         * 但是在多线程中,当两个线程同时判断instance == null时,会创建两个不同的实例
         * 如果我们在构造方法中什么也不做,就算两个对象不同,似乎也不会出什么问题啊
         * 但是如果我们在构造方法中做一些运算的操作,那么创建两次对象,运算的结果就会出错
         *
         */
        if(instance == null){
            instance = new SingletonLazy2();
        }
        return instance;
    }
}

5.懒汉模式3

/**
 * 懒汉模式 -->双重同步锁单例模式
 * 单例实例在第一次使用时创建
 */
/*
    也是线程不安全的
 */
public class SingletonLazy3 {
    //私有化构造函数
    private SingletonLazy3(){
    }
    /**
     *  分析:instance = new SingletonLazy3();操作其实是分三步完成的
     *  1.分配对象的内存空间, memory = allocate();
     *  2.初始化对象 ,ctorInstance();
     *  3.设置instance指向刚分配的内存, instance = memory
     *  在单线程情况下,是没有问题的
     *  但是在多线程情况下,由于JVM和cpu优化,会发生指令重排。执行顺序发生了变化。
         *  1.分配对象的内存空间, memory = allocate();
         *  2.设置instance指向刚分配的内存, instance = memory
         *  3.初始化对象 ,ctorInstance();
     *  有可能会出现问题的情况:
     *      当线程A执行到instance = new SingletonLazy3();时,执行到了第二步:设置instance指向刚分配的内存
     *      线程B执行到第一个instance == null,由于instance = memory,所以这时线程B直接返回instance
     *      但是,线程A还没有执行步骤三:初始化对象。这就会有问题。
     *
     */

    //定义单例对象
    private static SingletonLazy3 instance = null;
    //静态工厂方法
    public static SingletonLazy3 getInstance(){
        /*
            缩小synchronized作用范围
            并使用双重检验机制
         */
        if(instance == null){
            synchronized (SingletonLazy3.class){
                if (instance == null){
                    instance = new SingletonLazy3();
                }
            }
        }
        return instance;
    }
}

6.懒汉模式4

/**
 * 懒汉模式 -->双重同步锁单例模式+volatile
 * 单例实例在第一次使用时创建
 */
/*
    变成线程安全的
 */
public class SingletonLazy4 {
    //私有化构造函数
    private SingletonLazy4(){
    }
    //定义单例对象
    /*
        用volatile修饰,防止发生指令重排。这样就是贤臣安全的了
     */
    private volatile static SingletonLazy4 instance = null;

    //静态工厂方法
    public static SingletonLazy4 getInstance(){
        /*
            缩小synchronized作用范围
            并使用双重检验机制
         */
        if(instance == null){
            synchronized (SingletonLazy4.class){
                if (instance == null){
                    instance = new SingletonLazy4();
                }
            }
        }
        return instance;
    }
}

7.枚举模式

/**
 * 枚举模式 --最安全
 * 实际使用时才初始化对象,
 */
//线程安全的,推荐这种。
public class SingletonEnum {
    //私有构造方法
    private SingletonEnum(){
    }

    public static SingletonEnum getInstance(){
        return Singleton.INSTANCE.getSingleton();
    }
    private enum Singleton{
        INSTANCE;
        private SingletonEnum singleton;
        //JVM保证这个方法绝对只被调用一次
        Singleton(){
            singleton = new SingletonEnum();
        }
        public SingletonEnum getSingleton(){
            return singleton;
        }
    }
}

原文地址:https://www.cnblogs.com/inspred/p/9520845.html