美文网首页java面试
三、安全发布对象

三、安全发布对象

作者: AKyS佐毅 | 来源:发表于2018-03-23 14:24 被阅读180次

    1、 基础概念理解

    有些时候,我们希望在多个线程间共享对象,此时必须确保安全地进行共享,那么就牵扯到对象的发布问题。

    • 发布对象:使一个对象能够被当前范围之外的代码所使用。
    • 对象溢出:一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见。

    2、不安全的发布

    • 发布对象
      代码如下:
    @Slf4j
    @NotThreadSafe
    public class UnsafePublish {
    
       private String[] states = {"a", "b", "c"};
    
       public String[] getStates() {
           return states;
       }
    
       public static void main(String[] args) {
           UnsafePublish unsafePublish = new UnsafePublish();
           log.info("{}", Arrays.toString(unsafePublish.getStates()));
    
           unsafePublish.getStates()[0] = "d";
           log.info("{}", Arrays.toString(unsafePublish.getStates()));
       }
    }
    
    • 对象逸出
    @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();
       }
    }
    
    • 包含了对封装实例的隐藏和引用,这样在对象没有被正确构造完成之前就会被发布,由此导致不安全的因素在里面。
    • 1.导致this引用在构造期间溢出的错误,他是在构造函数构造过程中启动了一个线程,造成this引用的溢出。
    • 新线程只是在对象构造完毕之前就已经看到他了,所以如果要在构造函数中创建线程,那么不要启动它, 而是应该才用一个专有的start,或是其他的方式统一启动线程。
    • 使用工厂方法和私有构造函数来完成对象创建和监听器的注册来避免不正确的发布。

    3、安全发布的常用模式

    • 可变对象必须通过安全的方式来发布,这通常意味着在发布和使用该对象的线程时都必须使用同步。

    • 使用对象的线程能够看到该对象处于已发布的状态,并稍后介绍如何在对象发布后对其可见性进行修改。

    • 要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布。

      • 在静态初始化函数中初始化一个对象的引用。

      • 将对象的引用保持到volatile类型的域或者AtomicReference对象中。

      • 将对象的引用保存到某个正确构造对象的final类型域中。

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

    • 在线程安全容器内部的同步意味着,将对象放入到某个容器,例如Vector或synchronizedList时,将满足最后一条需求。如果线程A将对象X放入到一个线程安全的容器,随后线程B读取这个对象,那么可以确保B看到A设置的X状态,即便在这段读/写X的应用程序代码中没有包含显式的同步。

    • 通过讲一个键或者值放入Hashtable,synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问,还是通过迭代器访问)。通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。

    • 类库中的其他数据传递机制(例如Future和Exchanger)同样能实现安全发布。

    • 通常要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化:

    • 静态初始化由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。

    下边我们使用单例模式来详细说明

    /**
     * 懒汉模式
     * 单例实例在第一次使用时进行创建
     */
    @NotThreadSafe
    public class SingletonExample1 {
    
        // 私有构造函数
        private SingletonExample1() {
    
        }
    
        // 单例对象
        private static SingletonExample1 instance = null;
    
        // 静态的工厂方法
        public static SingletonExample1 getInstance() {
            if (instance == null) {
                instance = new SingletonExample1();
            }
            return instance;
        }
    }
    
    /**
     * 饿汉模式
     * 单例实例在类装载时进行创建
     */
    @ThreadSafe
    public class SingletonExample2 {
    
        // 私有构造函数
        private SingletonExample2() {
    
        }
    
        // 单例对象
        private static SingletonExample2 instance = new SingletonExample2();
    
        // 静态的工厂方法
        public static SingletonExample2 getInstance() {
            return instance;
        }
    }
    
    /**
     * 饿汉模式
     * 单例实例在类装载时进行创建
     */
    @ThreadSafe
    public class SingletonExample6 {
    
        // 私有构造函数
        private SingletonExample6() {
    
        }
    
        // 单例对象
        private static SingletonExample6 instance = null;
    
        static {
            instance = new SingletonExample6();
        }
    
        // 静态的工厂方法
        public static SingletonExample6 getInstance() {
            return instance;
        }
    
        public static void main(String[] args) {
            System.out.println(getInstance().hashCode());
            System.out.println(getInstance().hashCode());
        }
    }
    
    /**
     * 懒汉模式
     * 单例实例在第一次使用时进行创建
     */
    @ThreadSafe
    @NotRecommend
    public class SingletonExample3 {
    
        // 私有构造函数
        private SingletonExample3() {
    
        }
    
        // 单例对象
        private static SingletonExample3 instance = null;
    
        // 静态的工厂方法
        public static synchronized SingletonExample3 getInstance() {
            if (instance == null) {
                instance = new SingletonExample3();
            }
            return instance;
        }
    }
    
    /**
     * 懒汉模式 -》 双重同步锁单例模式
     * 单例实例在第一次使用时进行创建
     */
    @NotThreadSafe
    public class SingletonExample4 {
    
        // 私有构造函数
        private SingletonExample4() {
    
        }
    
        // 1、memory = allocate() 分配对象的内存空间
        // 2、ctorInstance() 初始化对象
        // 3、instance = memory 设置instance指向刚分配的内存
    
        // JVM和cpu优化,发生了指令重排
    
        // 1、memory = allocate() 分配对象的内存空间
        // 3、instance = memory 设置instance指向刚分配的内存
        // 2、ctorInstance() 初始化对象
    
        // 单例对象
        private static SingletonExample4 instance = null;
    
        // 静态的工厂方法
        public static SingletonExample4 getInstance() {
            if (instance == null) { // 双重检测机制        // B
                synchronized (SingletonExample4.class) { // 同步锁
                    if (instance == null) {
                        instance = new SingletonExample4(); // A - 3
                    }
                }
            }
            return instance;
        }
    }
    
    /**
     * 懒汉模式 -》 双重同步锁单例模式
     * 单例实例在第一次使用时进行创建
     */
    @ThreadSafe
    public class SingletonExample5 {
    
        // 私有构造函数
        private SingletonExample5() {
    
        }
    
        // 1、memory = allocate() 分配对象的内存空间
        // 2、ctorInstance() 初始化对象
        // 3、instance = memory 设置instance指向刚分配的内存
    
        // 单例对象 volatile + 双重检测机制 -> 禁止指令重排
        private volatile static SingletonExample5 instance = null;
    
        // 静态的工厂方法
        public static SingletonExample5 getInstance() {
            if (instance == null) { // 双重检测机制        // B
                synchronized (SingletonExample5.class) { // 同步锁
                    if (instance == null) {
                        instance = new SingletonExample5(); // A - 3
                    }
                }
            }
            return instance;
        }
    }
    
    /**
     * 枚举模式:最安全
     */
    @ThreadSafe
    @Recommend
    public class SingletonExample7 {
    
        // 私有构造函数
        private SingletonExample7() {
    
        }
    
        public static SingletonExample7 getInstance() {
            return Singleton.INSTANCE.getInstance();
        }
    
        private enum Singleton {
            INSTANCE;
    
            private SingletonExample7 singleton;
    
            // JVM保证这个方法绝对只调用一次
            Singleton() {
                singleton = new SingletonExample7();
            }
    
            public SingletonExample7 getInstance() {
                return singleton;
            }
        }
    }
    

    微信扫码关注java技术栈,每日更新面试题目和答案,并获取Java面试题和架构师相关题目和视频。

    相关文章

      网友评论

        本文标题:三、安全发布对象

        本文链接:https://www.haomeiwen.com/subject/ppfxcftx.html