注意:
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