一:单例模式介绍:
单例模式是模式应用最广的模式之一,也可能是很多初级工程师唯一会使用的设计模式。我们在应用单例模式的时候,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要一个全局的对象,例如在Android项目中:
一:我们假设有一个用户管理类,里面主要是用来处理当前是否是登录状态的的一些信息。因为在很多地方都需要我们去判断当前用户是否是登录状态。这里我们就可以吧这个用户管理类写成一个单例,保证该对象只会new一次。
二:应用只有一个ImageLoader实例,这个ImageLoader中又包含线程池,网络请求等等,很消耗资源。因为没必要每次使用都全构造多个实例,所以用一个单例模式再好不过了。
二:单例模式的几个关键点:
- 构造函数不对外开放,一般为Private。
- 通过一个静态方法或者枚举返回单例类对象。
- 确保单例类的对象有且只有一个,尤其是在多线程环境下。
- 单例类对象在反序列化时不会重新构建对象。**
三:单例模式的7种使用
1.懒汉模式–
懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance的时候进行初始化,而且上述的在声明静态对象的时候就已经初始化。我们会发现第一次调用时就会被初始化ins,每次调用getInstance方法都会进行同步,这样会消耗不必要的资源。
缺点:第一次加载时需要及时的进行实例化,反应稍慢,最大问题每次调用getInstance都会进行同步,造成没必要的开销。不建议使用。
public class Singleton{
private static Singleton ins;
private Singleton(){}
public static synchroized Singleton getInstance(){
if(ins==null){
ins=new Singleton();
}
return ins;
}
}
2.懒汉模式–
缺点:在多线程中不能正常使用
public class Singleton{
private static Singleton ins;
private Singleton(){}
//只有static修饰
public static Singleton getInstance(){
if(ins==null){
ins=new Singleton();
}
return ins;
}
}
3.DCL方式实现单例(双重检查锁定)
我们可以看到getInstance方法中对sin进行了两次判空:第一层判断主要是为了避免不必要的同步,第二层的判断是为了在null的情况下创建实例。这里主要是在JDK1.5之后才能正常使用。
假设线程A执行到 sin=new Singleton(),最终编译的时候大致做了3件事情:
1.给Singleton实例分配内存
2.调用SIngleton构造函数,初始化。
3.将sin对象指向分配的内存空间(此时sin就不是null了)
由于Java编译器永续处理器乱序执行,在JDK1.5之前JMM中的Cache,寄存器到主内存回写顺序的规定,上面的第二和第三的顺序是无法保证的。执行顺序有可能是1-2-3,有可能是1-3-2。所以会导致很多问题,有兴趣的朋友可以查看关于这块的问题。
JDK1.5之后官方主要到这种问题调整了JMM,具体优化了volatile关键字,所以像下面这样修饰sin可以保证sin对象每次都是从主内存中读取,就可以使用DCL的写法来完成单例.
优点:资源利用率高,第一次执行getInstance时单例才会被实例化,效率高.
缺点:第一次加载反应稍慢,也由于java内存模型的原因偶尔会失败,在高并发环境下也有一定的缺陷,虽然发生概率很小。除非在JDK低于1.6版本下使用,否则这种方式一般能满足需求。
public class Singleton{
private volatile static Singliton sin=null;
private Singleton(){}
public static Singleton getInstance(){
if(sin==null){
synchroized (Singleton.class){
if(sin==null){
sin=new Singleton();
}
}
}
return sin;
}
}
4.饿汉模式
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的.习惯使用这种。
public class Singleton{
private static Singleton sin=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return sin;
}
}
5.枚举单例
写法简单是枚举单例的最大优点,枚举在java中与普通类是一样的,不仅能够有字段,还能够有自己的方法。最重要的使默认枚举实例的创建时线程安全的,并且在任何情况下它都是一个单例。反序列化可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。而枚举不会存在这个问题,因为即使反序列化它也不回重新生成新的实例。只不过通常我们很少用。
public enum SingletonEnum{
INSTANCE;
public void doSomething(){
System.out.println("");
}
}
6.静态内部类单例模式
当第一加载Singleton类并不会初始化sin,只有在第一次调用getInstance方法时候才会导致sin被初始化,因此第一次调用getInstance方法会导致虚拟机加载SingLetonHo类,这种方式不仅能确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化。所以这是推荐使用的单例模式实现方式。
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return SingletonHo.sin;
}
//静态内部类
private static class SingletonHo(){
private static final Singleton sin=new Singleton();
}
}
7.使用容器实现单例模式
将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使我们可以管理多种类型的单例。降低了用户的使用成本,也降低了耦合度。
public class SingletonManager{
private static Map<String,Object> object=new HashMap<String,Object>();
private Singleton(){}
public static void registerService(string key,Objectinstance){
if(!object.containsKey(key)){
object.put(key,instance);
}
}
public sttic ObjectgetService(String key){
return object.get(key);
}
}
四:总结
不管以哪种形式实现单例模式,他们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中必须保证线程安全,防止反序列化导致重新生成实例对象等问题。选择哪种要看项目的本生,现在一般JDK’的版本都不会太低,所以很多单例模式都可以使用,具体看自己了。