单例顾名思义只能有单一的实例,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为

使用场景:如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取;还有如数据库连接池也是采用单例模式,只有一个连接池就可以了,再多就是浪费系统资源;还有如spring中的bean默认是单例;线程池;

单例的优点:
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.提供了对唯一实例的受控访问
3.由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能
4.避免对共享资源的多重占用
单例的缺点:
1.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难
2.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
实现单利模式的原则:
a.私有构造方法
b.私有静态引用指向自己实例
c.以自己实例为返回值的公有静态方法

饿汉式:单例实例在类装载时就构建,急切初始化

public class SingleOne {
    /**私有化构造方法*/
    private SingleOne(){}
    /**类加载就实例化,私有静态引用指向自己实例 */
    private static SingleOne instance = new SingleOne();
    /**以自己实例为返回值的公有静态方法*/
    public static SingleOne getInstance(){
        return instance;
    }
}

验证是否是单例:发现内存地址值是一样,说明多次获取是一个实例对象。

java 连点数据库数据重复 java数据库连接池单例_设计模式


优点

1.线程安全

2.在类加载的同时已经创建好一个静态对象,调用时反应速度快

缺点 : 资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化 。


懒汉式:单例实例在第一次被使用时构建,延迟初始化。

public class SingleTwo {
    /**私有化构造方法*/
    private SingleTwo(){}
    /**
     *私有静态引用指向自己实例
     *volatile 禁止指令重排(多线程安全)
     */
    private static volatile SingleTwo instance = null;
    /**以自己实例为返回值的公有静态方法*/
    public static SingleTwo getInstance(){
        //多个线程判断instance都为null时,在执行new操作时多线程会出现重复情况
        /*if(instance == null){
            instance = new SingleTwo();
        }*/

        //==============================
        //双重检查,防止多线程时创建多个实例
        if(instance == null){
            synchronized (SingleTwo.class){
                if(instance == null){
                    instance = new SingleTwo();
                }
            }
        }
        return instance;
    }
}

优点:资源利用率高,不执行getInstance()不被实例,
缺点:一次加载时反应不够快

这种单例可以被反序列化破坏
public class SingleFive {
    public static void main(String[] args) {
        // 通过反序列化获取单例模式对象
        String path = "single";
        //通过SingleTwo 方式获取单例对象
        SingleTwo single1 = SingleTwo.getInstance();
        try{
            //序列化到文件
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
            objectOutputStream.writeObject(single1);
            ObjectInputStream result = new ObjectInputStream(new FileInputStream(path));
            SingleTwo resultSingleton = (SingleTwo) result.readObject();
            // 反序列化出来的对象,和原对象,不是同一个对象。
            result.close();
            System.out.println(single1);
            System.out.println(resultSingleton);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

只要单例类,加如下方法:

/**
* 反序列化 就会自动调用这个 readResolve方法来返回我们指定好的对象了, 单例规则也就得到了保证.
* */
private Object readResolve() throws ObjectStreamException {
   return instance;
}
还可以被反射破坏单例

使用静态内部类的方式可以防止被反射破坏单例(如果想要通过反射获取其静态内部类 SingletonStaticClass 并给其属性 singleton 赋值为 null 时,就会抛出异常 IllegalAccessException ,阻止将其值设置为 null)再优化如下:

public class Singleton implements Serializable {
    private static class SingletonStaticClass {
        private static final Singleton singleton = new Singleton();
    }
    private Singleton() {
        synchronized (Singleton.class) {
            if (SingletonStaticClass.singleton != null) {
                throw new RuntimeException("不能重复创建对象");
            }
        }
    } 
    public static Singleton getInstance() {
        return SingletonStaticClass.singleton;
    }
}

最建议的一种方法是使用 枚举 来实现单例,这种方式天然的防止通过反射来破坏单例

懒汉式的另一种实现方法:
public class SingleThree {
    /**私有化构造方法*/
    private SingleThree(){}
    
    /**内部类*/
    private static class SingleThreeHelp{
        static SingleThree instance = new SingleThree();
    }
    /**以自己实例为返回值的公有静态方法*/
    public static SingleThree getInstance(){
        return SingleThreeHelp.instance;
    }
}

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化instance,故而不占内存。即当SingleThree 第一次被加载时,并不需要去加载SingleThreeHelp,只有当getInstance()方法第一次被调用时,才会去初始化instance,第一次调用getInstance()方法会导致虚拟机加载SingleThreeHelp类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

所以要根据自己的实际场景来确定使用懒汉式还是饿汉式
总结: 一般采用饿汉式,若对资源十分在意可以采用静态内部类,不建议采用懒汉式及双重检测