单例模式:确保一个类只有一个实例,而且自行实例化。
单例模式是我们用的最多的模式,也应该最先了解的模式。它的实现有很多种,我们一一道来,置于使用场景我们在优点中一起分析。在实现单例之前我们首先需要注意以下几点:
1. 构造器私有化-------一个项目只需要一个该类实例,确保不能随便new出一个新实例;
2. 建一个静态方法返回单例类对象--------该项目中可以获得这个类实例的唯一方法;
3. 确保项目中只有一个实例,尤其多线程情况下;
4. 防止单例模式被反射或者反序列化时重新创建对象。
饿汉式:
public class Singleton{
//静态实例对象
private static Singleton instance=new Singleton();
//私有化构造器
private Singleton(){}
//公有的静态方法,暴露给使用者
public static Singleton getInstance(){
return instance();
}
}
其实饿汉式是非常不错的单例模式,保证了一个项目中多线程情况下仍是只有一个实例。一个小缺点是静态中直接实力化,如果没用这个类,那就浪费资源了。
懒汉式:
public class Singleton{
//申明成员变量
private Singleton instance;
//私有化构造器
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
懒汉式最大的区别就是使用者调用暴露的静态方法时,才会实力化该类。synchronized确保了实例只有一个,但是太耗费资源。每次获取该实例都会进行同步。所以有了双重检索单例
Double check Lock (双重检索机制)-------------使用最多的单例:
public class Singleton(){
//使用volatile 避免处理器指令重排
private volatile static Singleton instance = null;
//私有化构造器
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance =new Singleton();
}
}
}
return instance;
}
}
作为最常用的单例模式,我们需要详细讲解一下。
第一点:synchronized 锁的是 Singleton 的 class对象;
第二点:synchronized 中的判空,如果两个线程同时获得锁(前提是这个类一定还没实例化),这样的情况下不判空就会两个线程 实例化两次;
第三点:volatile 在JDK 1.5后具体化了这个关键字,它在这的用处是使得JVM不能指令重排。
因为:instance =new Singleton(); 这条语句在JVM操作的时候其实是分三步执行的(JVM的相关知识,这两天也会更, 可以关注一下,不迷路,哈哈哈!!!)。指令大概是:
( 一 ) 给Singleton对象分配内存;
( 二 ) 调用构造器,初始化该实例;
( 三 ) 将instance 指向该对象;
一般情况,指令顺序是这三步。但是,CPU和JVM会对指令顺序优化,而做指令重排。那么,执行顺序可能就是步奏
1 - 3 - 2。这样的话如果线程A 执行到第三步,第二步没执行呢,切换到B线程。B线程判断 instance 不为空,但是这 个指向的对象还没实力化,这就会出现问题。这就是volatile的作用。
静态类模式:
public class Singleton{
//私有化构造器
private Singleton(){}
//使用静态内部类
private static class SingletonHodler{
private static final Singleton instance = new Singleton();
}
public static getInstence(){
return SingletonHolder.instance;
}
}
使用静态内部类实现了唯一实例化,防止了多线程,推荐使用。
枚举单例:
public enum Singleton{
INSTANCE;
}
枚举最大的优点就是: 可以防止反射和反序列化,确点性能差;
其他类如果想反序列化加入如下方法:
private Object readResolve() throws ObjectStreamException{
return instance;
}
单例的优点:
1. 单例模式只会有一个实例,减少了内存开支,尤其创建频繁的类;
2. 读取配置这种情况,可以直接缓存在单例中,减速开支;
3. 减少了多重占用,例如一个写操作,避免了一个资源被多个对象操作;
4. 单例可以作为全局的访问点,作为一个共享资源。
缺点:
1. 单例模式扩展困难,违背了对修改关闭;
2. 如果持有Context,会造成内存泄露,所以可以用ApplicationContext;