注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
考查的核心知识点:
单例模式 : 顾名思义, 就是在整个运行时域(runtime), 一个类只有一个实例对象.
为什么需要单例模式 : 有的类的创建和销毁对资源来说消耗不大, 比如String; 有的类就比较庞大和复杂. 如果频繁的创建和销毁这些对象, 并且这些对象是完全可以复用的情况下, 将会造成不必要的性能浪费.
例子:
创建一个数据库的链接对象, 只需用单例模式创建一次就OK.
多种写法,多种思维.
要实现单例模式, 主要考虑3点:
public class Singleton {
    
    private Singleton() {
        // 构造器私有--1
    }
    // 初始化对象为null
    private static Singleton instance = null;
    // 通过getInstance()方法来使用Singleton对象
    public static Singleton getIntance() {
        // 判断instance是否被构造过.
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    
}
1.外部无法 Singleton s = new Singleton();
实例对象是第一次被调用的时候才真正构建, 而不是程序一启动就构建好等你调用. 这种滞后的加载就是懒加载.
public class Singleton {
    public Singleton() {}
    public static Singleton instance = null;
    public static synchronized getIntance() {
        if(instance == null){
            Singleton s = new Singleton();
        }
        return instance;
    }
}
这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低. 每次获取对象是都要进行同步操作, 对性能影响非常大.
public void Singleton {
    // 编译期构建
 	private static Singleton instance = new Singleton();
    private Singleton(){}
    public static Singleton getInstance() {
        return instance;
    }
}
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
拓展:
对象在类中被定义为private static,通过getInstance(),通过java的classLoader机制保证了单例对象唯一。
有没有即是线程安全, 又是懒加载的单例模式, 双检锁就出现了.
改造 懒汉式:(线程安全的)
public class Singleton {  
    private static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        
        // 调用时构建,if里有多个线程,a执行完后,b也执行.导致重复构建.
        synchronized (Singleton.class) {
            singleton = new Singleton();  
        }  
    }  
    return singleton;  
    }  
}
使用双if:
public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if(singleton == null) {
            synchronized (Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton(); 
                }
            }  
        }
        return singleton;
    }
}
//还会有一个指令重排序问题.
为什么要使用volatile修饰?
虽然已经使用synchronized进行同步,但在创建singleton对象时,会有下面的伪代码:
memory=allocate(); // 1:分配对象的内存空间
ctorInstance();   // 2: 初始化对象
singleton=memory; // 3: 设置instance指向刚分配的内存地址
当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。
如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!
使用了volatile就能阻止作用在instance上的指令重排问题.
public class Singleton {
    private volatile static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if(singleton == null) {
            syschornized (Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton(); 
                }
            }  
        }
        return singleton;
    }
}
在程序启动是不会加载, 只有在第一调用时才会加载.
public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}
//线程安全的,懒加载.
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
不能通过反射破坏
无法满足懒加载
自动避免序列化/反序列化攻击
public enum Singleton {  
    INSTANCE;    
}
原文:https://www.cnblogs.com/gzp5608/p/13784113.html