设计模式——单例模式

单例模式

单例模式是指Java虚拟机中一个类只有一个实例。

饿汉式

// 1. 饿汉式
public class Singleton1 {
//私有构造方法
    private Singleton1() {
        System.out.println("private Singleton1()");
    }
//私有静态变量
    private static final Singleton1 INSTANCE = new Singleton1();
//公开获取对象的方法
    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

实现要点:

  1. 构造方法是私有的,不然其他类有机会调用构造方法来创造实例对象
  2. 提供一个静态的成员变量INSTANCE,成员变量的类型就是单例类型,值就是用私有构造方法创建出来的一个唯一实例。
  3. 因为静态变量一般是私有的不能直接访问,一般会提供公共的静态方法,方法名就叫getInstance()获得实例,实现是返回刚刚的成员变量。

所谓饿汉式是相对于懒汉式来说的,懒汉式是指当第一次调用getInstance()方法时才会去创建实例。而饿汉式则不是,只要类加载初始化了,这个实例就能被创建。

这里的otherMethod()方法是为了测试用的。在测试中先调用otherMethod(),这时候类就会被初始化,执行类初始化操作的时候就会导致

反射破坏单例及解决办法

反射直接调用私有的构造方法破坏单例。

reflection(Singleton1.class);


    private static void reflection(Class<?> clazz) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//获取无参构造方法
        Constructor<?> constructor = clazz.getDeclaredConstructor();
//使私有的构造方法也可以被使用
        constructor.setAccessible(true);
//调用构造方法的newInstance()
        System.out.println("反射创建实例:" + constructor.newInstance());
    }

可以预防这种情况的发生:在构造方法中做个判断

public class Singleton1 {
    private Singleton1() {
//增加判断解决反射破坏单例
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }

    private static final Singleton1 INSTANCE = new Singleton1();

    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
    }
}

反序列化破坏单例

 一旦对象实现了反序列化接口----->implements Serializable------>就有可能被利用反序列化来破坏单例:因为反序列化创建对象是不走构造方法的

 // 反序列化破坏单例
        serializable(Singleton1.getInstance());


private static void serializable(Object instance) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
//把对象变成字节流
        oos.writeObject(instance);
//再把字节流还原成一个实例
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        System.out.println("反序列化创建实例:" + ois.readObject());
    }

预防的方法:在类中写一个方法readResolve(),在反序列化创建对象的时候如果发现了类中有重写这个方法,就会把这个方法的返回值作为结果返回

    public Object readResolve() {
        return INSTANCE;
    }

Unsafe 破坏单例

unsafe是JDK内置的一个类,也是不会走类的构造方法创建实例。

// Unsafe 破坏单例
        unsafe(Singleton1.class);
 

    private static void unsafe(Class<?> clazz) throws InstantiationException {
        Object o = UnsafeUtils.getUnsafe().allocateInstance(clazz);
        System.out.println("Unsafe 创建实例:" + o);
    }

枚举饿汉式 

 只定义了一个变量INSTANCE,这样就控制了只有唯一的实例

// 2. 枚举饿汉式
public enum Singleton2 {
    INSTANCE;

}

枚举类不会被反序列化破坏单例,因为在反序列化中遇到的如果是枚举类会特殊处理,直接返回。

反射也不会破坏单例,如果反射中发现是枚举类会直接抛异常。

unsafe会破坏单例。

懒汉式单例 

// 3. 懒汉式单例
public class Singleton3 implements Serializable {
    private Singleton3() {
        System.out.println("private Singleton3()");
    }

    private static Singleton3 INSTANCE = null;

    // Singleton3.class
    public static  Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }

}

懒汉式单例是指当第一次调用getInstance()方法时才会去创建实例,那么在一开始的私有变量INSTANCE中就先不赋值,在调用getINSTANCE()的时候判断是否为null,如果是就创建,否则什么都不做。

但是在多线程的时候会出现并发安全问题。考虑如下情况:有两个线程,在判断INSTANCE==null中,此时为true,线程一准备去创建实例对象,这时候切换到线程二,因为线程一的对象创建还没完成,所以线程二的判断也为true,也进入到了对象的创建。解决办法:在getINSTANCE()方法加上synchronized关键字,就可以加上线程安全的保护。原理:加在静态方法上的synchronized就是相当于在singleton3.class上加了一把锁,当线程一运行到这个方法时发现是静态方法而且这里加了synchronized关键字,就会去尝试获取singleton3.class的锁,然后去运行;如果有其他线程来到了这个方法也要去判断是否能拿到这个锁。但是这样性能并不好,因为这种情况只有发生在第一次创建INSTANCE时才会出现,所以希望加锁是只针对第一次创建INSTANCE的情况。

 DCL懒汉式单例

对上一种懒汉式的优化 ,双检锁懒汉式。加锁之前先判断INSTANCE是否为null,如果不为null就直接返回了,如果为null,就进入synchronized方法

// 4. 懒汉式单例 - DCL
public class Singleton4 implements Serializable {
    private Singleton4() {
        System.out.println("private Singleton4()");
    }

    private static volatile Singleton4 INSTANCE = null; // 可见性,有序性

    public static Singleton4 getInstance() {
        if (INSTANCE == null) {
//如果为null证明是第一次进入
            synchronized (Singleton4.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton4();
                }
            }
        }
        return INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

为什么在synchronized方法还要再次判断INSTANCE是否为null?假设没有这次判断,在竞争锁后线程一进入了代码块并创建出INSTANCE,释放锁,线程二获取锁之后也会再次创建INSTANCE。

为何要加volatile

 创建对象方法对应的Java底层指令是创建对象(计算需要多少空间内存等),调用构造(给成员变量赋值)、给instance赋值。CPU执行的时候可能会对指令的执行进行优化,调换指令执行的次序。因为给成员变量赋值和给instance赋值没有因果关系,所以执行的时候就有可能调换这两条指令的顺序,这种调换在单线程下没有影响,但是在多线程下就不一定了

  • INSTANCE = new Singleton4() 不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造

  • 如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null 时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象

 加上volatile就是对修饰的变量的赋值语句之后加上一个内存屏障,阻止之前的赋值语句越过屏障

饿汉式的实例创建是放在静态代码块中实现,而静态代码块的线程安全JVM会保证,而懒汉式就要考虑,于是有了内部类懒汉式。

内部类懒汉式单例

// 5. 懒汉式单例 - 内部类
public class Singleton5 implements Serializable {
    private Singleton5() {
        System.out.println("private Singleton5()");
    }

    private static class Holder {
        static Singleton5 INSTANCE = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return Holder.INSTANCE;
    }

    public static void otherMethod() {
        System.out.println("otherMethod()");
    }
}

 在单例对象内部创建一个静态内部类,因为内部类可以访问外部类的私有方法,所以在内部类里创建单例对象并赋值给静态变量,所以是线程安全的

JDK中单例模式的体现

  • Runtime 体现了饿汉式单例

    public class Runtime {
        private static Runtime currentRuntime = new Runtime();
    
        /**
         * Returns the runtime object associated with the current Java application.
         * Most of the methods of class <code>Runtime</code> are instance
         * methods and must be invoked with respect to the current runtime object.
         *
         * @return  the <code>Runtime</code> object associated with the current
         *          Java application.
         */
        public static Runtime getRuntime() {
            return currentRuntime;
        }
    
        /** Don't let anyone else instantiate this class */
        private Runtime() {}

  • Console 体现了双检锁懒汉式单例(volatile是明显的标志)

        private static volatile Console cons = null;
        /**
         * Returns the unique {@link java.io.Console Console} object associated
         * with the current Java virtual machine, if any.
         *
         * @return  The system console, if any, otherwise <tt>null</tt>.
         *
         * @since   1.6
         */
         public static Console console() {
             if (cons == null) {
                 synchronized (System.class) {
                     cons = sun.misc.SharedSecrets.getJavaIOAccess().console();
                 }
             }
             return cons;
         }

  • Collections 中的 EmptyNavigableSet 内部类懒汉式单例

    private static class EmptyNavigableSet<E> extends UnmodifiableNavigableSet<E>
                implements Serializable {
                private static final long serialVersionUID = -6291252904449939134L;
    
                public EmptyNavigableSet() {
                    super(new TreeSet<E>());
                }
    
                private Object readResolve()        { return EMPTY_NAVIGABLE_SET; }
            }
    

  • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例

        private static class ReverseComparator
            implements Comparator<Comparable<Object>>, Serializable {
    
            private static final long serialVersionUID = 7207038068494060240L;
    
            static final ReverseComparator REVERSE_ORDER
                = new ReverseComparator();
    
            public int compare(Comparable<Object> c1, Comparable<Object> c2) {
                return c2.compareTo(c1);
            }
    
            private Object readResolve() { return Collections.reverseOrder(); }
    
            @Override
            public Comparator<Comparable<Object>> reversed() {
                return Comparator.naturalOrder();
            }
        }

  • Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

    enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
            INSTANCE;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值