单例模式

单例模式:即是对于某对象的类只能允许一个实例(该对象)存在,这个对象即是单例。

有时对于整个系统只需要一个全局对象,这样有利于协调系统的整体行为,例如,某服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过单例对象来获取这些配置信息。这样就简化了系统的配置管理。

单例对象之所以能作为一个全局对象存在于系统中,就是利用了jvm垃圾回收机制不会回收单例对象。(只要有引用指向该对象,这就是一个或者的对象,??网上还有一种说法:static 属性不会被回收。不管怎样只要允许单例的存在就不影响我们使用它)

单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。

实现步骤:

  1.该类的构造方法私有化。这样其他地方无法通过该类的构造器来实例化一个对象,故而只能通过该类提供的静态方法来获得其对象;

  2.该类提供一个静态方法,当调用该方法时,如果类持有的引用不为空,就将其返回,否则,创建一个实例并保留其引用,然后返回。

注意:

  单例模式在多线程的应用场合下必须小心使用。要避免两个以上的线程同时操作以免违反单例的遵旨。

一、饿汉式

  优点:实现简单,装载时就完成实例化,避免了线程同步问题。

  缺点:没有Lazy—loading效果,若是系统用不到则会有资源浪费。

/**
静态常量
*/
public class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return INSTANCE;
    }
}
/**
静态代码块
*/
public class Singleton{
    private static Singleton instance;
    static{
        instance = new Singleton();
    }
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

二、懒汉式

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下, 一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。 所以在多线程环境下不可使用这种方式。

public class Singleton{
    private static Singleton singleton;
    private Singleton(){}
    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

再看这种写法:

public class Singleton{
    private static Singleton singleton;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

这里用了关键字 synchronized 做了个线程同步,缺点: 效率太低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。 若是引用有对象直接return。

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

这里为了提高效率将线程锁直接加在了实例化代码,但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行, 另一个线程也通过了这个判断语句,这时便会产生多个实例。

public class Singleton {
    private static volatile Singleton singleton;
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查, 这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。这里可以这样说 外层 if 语句用来提高效率,里层 if 语句用来确保单例。

说完了饿汉懒汉,还有:

静态内部类

public class Singleton{
    private Singleton(){}
    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton(); 
    }
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

这种方式跟饿汉式类似,但又有不同:两者都利用类加载机制来确保实例化时只有一个线程,不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。

枚举

public enum Singleton{
    INSTANCE;
    public void whateverMethod() {
     
    }
}

不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。不过实际项目中用的很少。

原文地址:https://www.cnblogs.com/lightandtruth/p/8446979.html