Java单例模式(设计模式之单例模式)

1.设计模式之终极结合

最近几期博客打算主要讲一下单例模式观察者模式Build模式,目的是为了方便后期为大家带来RXJava+Retrofit2.0+Okhttp3结合使用的网络请求框架。

2.单例模式介绍

单例模式是我们使用的最娴熟的模式之一,也是我们App开发者必须会的模式之一。单例模式顾名思义就是在一个App/Project中一个单例对象类只存在一个实例。比如我们一个App中是不是只有一个Application,里面的信息可以应用于整个App,这样我们可以节约很多资源。再比如我们一个App中是不是应该只有一个Glide框架,Glide是一个集缓存、网络请求、线程池等于一身的图片加载框架(类似于以前的ImageLoader现在已经不推荐使用了);这些都是非常消耗资源的,你不可能还实例化多个实例吧。这种为了节约资源应用于整个project,在整个project中只存在一个实例的模式就是单例模式。

3.单例模式定义

一个类中有且只有一个实例,当调用这个类时可以自动化实例并向整个Project(对于Android开发来说,准确一点是整个Module)提供这个实例。

4.单例模式使用最多的两种模式(双锁单例模式,静态内部类单例模式)

 

  • 双锁单例模式(Double Check Lock)

 使用多的原因:双锁模式线程安全且资源利用率高,只在第一次实例化的时候进行同步锁。例如Okhttp、Glide、EventBus等很多框架都有用到。

package com.admin;

import android.app.Application;

/**
 * app单例模式之DCL(顾名思义:双重判断加个锁)
 */
public class AppSingleton extends Application {
    /**jdk1.5之后加入关键字volatile解决jdk1.5之前java编译器允许乱序执行1.构造函数初始化成员字段
     *2.将singletonInstance对象分配到内存空间(分配内存之后就不为null了)
     *线程A中如果1,2的顺序可以先执行2再执行1,那么就存在singletonInstance还没初始化之前就已经不为空了
     * 所以如果是其他线程B调用就会出现直接把singletonInstance拿去使用,则线程B双锁模式失效,但是这种情况极少会出现。
     */
//    private static AppSingleton singletonInstance = null;
// 由于上述情况很少出现加入关键字volatile,多少会影响性能
// 所以这种情况下就建议您使用静态内部类单例模式(除非你并不在意这点性能)
    private volatile static AppSingleton singletonInstance = null;
    public AppSingleton() {
    }

    public static AppSingleton getSingletonInstance() {
        //第一次判空,避免不必要的同步
        if (singletonInstance == null) {
            //同步锁
            synchronized (AppSingleton.class) {
                //第二次判空,进行实例化对象
                if (singletonInstance == null) {
                    singletonInstance = new AppSingleton();
                }
            }
        }
        return singletonInstance;
    }
}

 总结:优点在于DCL解决了资源消耗,避免了多于的同步,线程安全问题;缺点在于某些情况下存在失效问题(多线程同时调用单例类,这就是所谓的并发现象),这种情况下建议使用静态内部类单例模式(绝大多数DCL还是不错的)。

  • 静态内部类单例模式

使用多的原因:DCL模式可以用的地方,静态内部类也可以用;并且在并发严重的情况下可以替代DCL模式。

package com.admin;

import android.app.Application;

/**
 * app单例模式之静态内部类模式
 */
public class AppSingleton extends Application {

    private AppSingleton(){

    }
    /**
     * 静态内部类(题外话:为啥我们要用静态内部类啦,防止内存泄漏,保证线程安全。好习惯是慢慢积累的。)
     */
    private static class SingletonApp{
        private static final AppSingleton singletonInstance = new AppSingleton();
    }
    //外部直接AppSingleton.getSingletonInstance()获取单例
    public static AppSingleton getSingletonInstance() {
        return SingletonApp.singletonInstance;
    }
}

总结:当AppSingleton第一次被加载时,并不会初始化singletonInstance,只有当getSingletonInstance()方法第一次被调用时,才会去初始化singletonInstance,第一次调用getSingletonInstance()方法会导致虚拟机加载SingletonApp类进行初始化。这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化,所以推荐使用静态内部类实现单例模式。如果担心什么获取不到上下文,不便于传参什么的,你可以在单例类里面写一个获取上下文的方法不就行了。

5.不常用单例模式(饿汉式、懒汉式、枚举、容器)

  • 饿汉式单例模式

不推荐使用原因不管怎样只要你调用单例类就会进行实例化对象,这样如果不使用的话就会造成不必要的内存浪费。

package com.admin;

import android.app.Application;

/**
 * app单例模式之饿汉式
 */
public class AppSingleton extends Application {
//先实例化再说,管你那么多;这样就会造成如果不使用这个对象就会浪费资源
    private static final AppSingleton singletonInstance = new AppSingleton();

    private AppSingleton() {
    }
    
    public static  AppSingleton getSingletonInstance() {
        
        return singletonInstance;
    }
}

总结:不建议使用,虽然线程安全(只要进行类调用就已经实例化了),但是会有不必要的内存开销。

  • 懒汉式单例模式

不推荐使用原因:不管多线程同时调用就会实例化多个对象那么会造成多个实例,这就不叫单例模式,耗内存,耗资源;线程不安全。加synchronized关键字避免多线程调用出现线程不安全,但是每次调用又会出现同步开销。

package com.admin;

import android.app.Application;

/**
 * app单例模式之懒汉式
 */
public class AppSingleton extends Application {
    private static AppSingleton singletonInstance;

    private AppSingleton() {
    }

    //加上了关键字synchronized解决了线程安全的问题,但是每次调用都会进行同步,造成不必要的同步开销(消耗资源)
    //不加关键字就存在线程安全问题(几个线程同时调用就会实例化出多个单例,这还能叫单例模式吗?耗内存,耗资源)
    public static synchronized AppSingleton getSingletonInstance() {
        if (singletonInstance == null) {
            singletonInstance = new AppSingleton();
        }
        return singletonInstance;
    }
}

总结:不建议使用,不加同步关键字会有线程不安全问题,加了会有不必要的同步开销问题,浪费资源。

  • 枚举单例模式(通过枚举实现单例)
package com.admin;

/**
 * app单例模式之枚举
 */

public enum AppSingleton {
    SINGLETON;
    //这里面你可以写很多方法,很多静态变量供整个项目使用
}

总结:别的不说,一看就知道:这方法简单粗暴。但是它不可以继承,这就有缺陷了啊 。

  • 容器单例模式(通过容器实现单例)
package com.admin;

import java.util.HashMap;
import java.util.Map;

/**
 * app单例模式之容器
 * 这种方式可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取
 * 降低了用户的使用成本,也隐藏了用户的具体实现,降低了耦合度
 */

public class AppSingleton {
    //初始化一个对象map
    private static Map objectMap = new HashMap<>();
    
    private AppSingleton(){}
    //通过key获取单例对象
    public static Object getAppSingleton(String key){
        return objectMap.get(key);
    }
    //用户需要根据不同的键(需求)来实例化单例
    public static void registerApp(String key , Object appSingletonInstance){
        if(!objectMap.containsKey(key)){
            objectMap.put(key,appSingletonInstance);
        }
    }
}

暂时还没用过这种模式就不做总结了。

总的来说根据不同的情况来选择不同的单例模式,现在在开发过程中基本上DCL和静态内部类模式使用最广泛。

最后感谢你看到这里,具体的实践需要等把剩下的几个设计模式讲完之后,再进行Rxjava+Retrofit+okhttp框架的讲解。

感谢:

《Android源码设计模式解析与实战》

《Java并发编程实践》

 

 

你可能感兴趣的:(设计模式)