美文网首页
Java多线程(6)

Java多线程(6)

作者: Cool_Pomelo | 来源:发表于2020-01-19 17:39 被阅读0次

    Java多线程(6)

    七种单例设计模式的设计

    饿汉式

    public class Singleton {
    
    //    实例变量
        private byte[] data = new byte[1024];
    
    //    定义实例对象的时候直接初始化
        private static Singleton instance = new Singleton();
    
    //    私有构造函数
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            return instance;
        }
    }
    
    
    

    饿汉式的关键在于instance作为类变量并且直接得到了初始化,如果主动使用Singleton类,那么instance实例将会直接完成创建

    insta作为类变量在类初始化的过程中会被收集劲< clinit >()方法中,该方法能够百分之百保证同步,也就是instance在多线程的情况下不可能被实例化两次

    懒汉式

    public final class Singleton {
        
        private byte[] data = new byte[1024];
        
    //    定义实例,但是不直接初始化
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if(null == instance){
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    
    

    懒汉式就是使用类实例的时候再去创建

    Singleton.class被初始化的时候instance并不会被实例化,但是getInstance方法放在多线程环境下分析,则会导致instance被实例化不止一次,并不能保证单例的唯一性

    懒汉式 + 同步方法

    public class Singleton {
        
        private byte[] data = new byte[1024];
        
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
    //    加入同步控制,每次只有一个线程能够进入
        public synchronized static Singleton getInstance() {
            if (null==instance){
                instance = new Singleton();
            }
            return instance;
        }
    }
    
    
    

    考虑到懒汉式无法保证实例的唯一性,此处使用synchronize关键字来修饰getInstance方法,来保证数据的同步性,但是这种方法性能低下。

    Double - Check

    public class Singleton {
    
        private byte[] data = new byte[1024];
    
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
    
    //        当instance为null,进入同步代码块,避免了每次都要进入
            if (null == instance){
    
    
    //            只有一个线程能够获得Singleton.class关联的锁
                synchronized (Singleton.class){
                    if (null == instance){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    
    

    双重检查机制首次初始化的时候加锁,之后则允许多个线程同时进行getInstance调用获取类的实例

    当两个线程发现null == instance时候,只有一个线程有资格进入同步代码块,完成instance的实例化,随后线程发现null == instance不成立时无需进行任何动作

    Volatile + Double - Check

    上述Double - Check写法看似解决了问题,但是有个很大的隐患。实例化对象的那行代码(instance = new Singleton();),实际上可以分解成以下三个步骤:

    • 分配内存空间
    • 初始化对象
    • 将对象指向刚分配的内存空间

    但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:

    • 分配内存空间

    • 将对象指向刚分配的内存空间

    • 初始化对象

    这种情况下对应到就是singleton已经不是null,而是指向了堆上的一个对象,但是该对象却还没有完成初始化动作。当后续的线程发现singleton不是null而直接使用的时候,就会出现意料之外的问题

    可以使用volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是禁止指令重排序,即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用

    
    public class Singleton {
    
        private byte[] data = new byte[1024];
    
        private volatile static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
    
    //        当instance为null,进入同步代码块,避免了每次都要进入
            if (null == instance){
    
    
    //            只有一个线程能够获得Singleton.class关联的锁
                synchronized (Singleton.class){
                    if (null == instance){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    

    Holder方式

    public class Singleton {
        
        private byte[] data = new byte[1024];
    
        private Singleton() {
        }
        
    //    在静态内部类中持有Singleton实例,并且直接进行初始化
        private static class Holder{
            private static Singleton instance = new Singleton();
        }
        
        
        public static Singleton getInstance(){
            return Holder.instance;
        }
    }
    
    
    

    Holder方式完全借助了类加载的特点

    此方式又可以保证数据的同步性,也能保证内存的可见性,指令顺序性,原子性

    枚举方式

    public enum Singleton {
        
        INSTANCE;
        
        private byte[] data = new byte[1024];
        
        Singleton()
        {
            System.out.println("INSTANCE will be 初始化");
        }
        
        public static void method(){
    //        调用该方法会主动使用Singleton,INSTANCE将被实例化
        }
        
        public static Singleton getInstance(){
            return INSTANCE;
        }
    }
    
    

    枚举类型不允许继承,同样是线程安全的且只能被实例化一次

    相关文章

      网友评论

          本文标题:Java多线程(6)

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