JAVA设计模式——单例模式

来源:互联网 发布:win10网络图标不能运行 编辑:程序博客网 时间:2024/06/06 00:56
转载请注明出处:http://blog.csdn.net/qq_35744386/article/details/78729154

设计模式–>单例模式

在整个系统中某一个类仅仅提供一个唯一的实例对象。一般我们会在这个类中写一个getInstence()方法来获取这个唯一实例对象。

实现方式分类

懒汉式单例类、饿汉式单例类、枚举单例类。

在Android开发中,我们也常常会用到SharePrefresence,一般来说将储存偏好的逻辑都存放在一个类中会比较好管理,对外只提供一个存储方法和一个读取方法即可,这时我们就可以用到单例模式。下面我们来看看具体实现。

懒汉式单例类

public class SharedPreferenceInstance {    private SharedPreferences mPreferences;    private static SharedPreferenceInstance mInstance = null;    //常量key,用来储存或取出数据    private static final String MAIN_CONTENT = "MAIN_CONTENT";    //私有化构造方法,避免外部调用    private SharedPreferenceInstance() {        mPreferences = PreferenceManager.getDefaultSharedPreferences(MyApplication.getApp().getApplicationContext());    }    //外部获取单例方法    public static SharedPreferenceInstance getInstance() {        if (mInstance == null) {            synchronized (SharedPreferenceInstance.class) {                if (mInstance == null) {                    mInstance = new SharedPreferenceInstance();                }            }        }        return mInstance;    }    //保存    public void saveContent(String content) {        if (mPreferences == null) return;        mPreferences.edit().putString(MAIN_CONTENT, content).apply();    }    //读取    public String getContent() {        if (mPreferences == null) return null;        return mPreferences.getString(MAIN_CONTENT, null);    }}

分析:可以看到getInstance()方法中,如果mInstance = null即没有实例化过,就新建一个。否则就将以前新建的实例返回。(同步相关知识文末有详细讲解)

饿汉式单例类

public class SharedPreferenceInstance {    private SharedPreferences mPreferences;    //声明时就新建实例    private static SharedPreferenceInstance mInstance = new SharedPreferenceInstance();    //常量key,用来储存或取出数据    private static final String MAIN_CONTENT = "MAIN_CONTENT";    //私有化构造方法,避免外部调用    private SharedPreferenceInstance() {        mPreferences = PreferenceManager.getDefaultSharedPreferences(MyApplication.getApp().getApplicationContext());    }    //外部获取单例方法    public static SharedPreferenceInstance getInstance() {        return mInstance;    }    //保存    public void saveContent(String content) {        if (mPreferences == null) return;        mPreferences.edit().putString(MAIN_CONTENT, content).apply();    }    //读取    public String getContent() {        if (mPreferences == null) return null;        return mPreferences.getString(MAIN_CONTENT, null);    }}

分析:可以看到与懒汉式单例类不同的是,在单例声明的同时就直接新建实例。在getInstance()方法中,直接返回该单例。
饿汉式与懒汉式是个形象的比喻,懒汉式当你需要我的时候我才新建实例;饿汉式不管你需不需要我一开始就新建实例。

枚举单例类

 使用枚举来实现单例会更加简单.
public enum SharedPreferenceInstance {    mInstance;    private SharedPreferences mPreferences;    //常量key,用来储存或取出数据    private static final String MAIN_CONTENT = "MAIN_CONTENT";    SharedPreferenceInstance() {        mPreferences = PreferenceManager.getDefaultSharedPreferences(MyApplication.getApp().getApplicationContext());    }    //外部获取单例方法    public static SharedPreferenceInstance getInstance() {        return mInstance;    }    //保存    public void saveContent(String content) {        if (mPreferences == null) return;        mPreferences.edit().putString(MAIN_CONTENT, content).apply();    }    //读取    public String getContent() {        if (mPreferences == null) return null;        return mPreferences.getString(MAIN_CONTENT, null);    }}

分析:使用枚举从根本上杜绝重复创建对象,枚举的单例性是由JVM来保证的。

线程同步与单例模式中的内存优化

线程同步:通俗的讲就是在同一时间保证仅有一条线程可以访问某个代码块。

大家都知道java中要想保证线程同步只需要在方法上加锁即可:

 public  synchronized void function(){        //具体方法}

举个例子:在商场买衣服,同一个试衣间同一时间仅仅只会有一位顾客正在使用,其实这就是一个同步的问题,我们看看是如何做到的,当然就是在试衣间的门上加了一道锁,当其他顾客看到试衣间门锁住了就自然明白有人正在使用,就会等待他人使用完毕再进入。拿到我们程序来说,当某一条线程访问了function方法,它就会在门上上锁,当其他线程尝试访问这个方法时,发现已经上了锁,就会排队等候,直到之前的线程将function方法执行完毕。好了我们对同步有了基本认识,那么我们就趁热打铁继续分析一下懒汉式单例类中的线程同步吧。
相信很多人都知道懒汉式单例类单例方法(getInstance()方法)还有这种写法:

 public static synchronized SharedPreferenceInstance getInstance() {        if (mInstance == null) mInstance = new SharedPreferenceInstance();        return mInstance;}

我们可以看到这种写法是直接在方法上加入了同步锁,使得同一时间,仅有一个线程能够进入这个方法,接下来我们分析一下这种写法的弊端。先拿之前商场买衣服的例子来说,这种写法就好比是我进入某一商铺后,就把商铺的大门给关上,等我选好衣服,试穿完毕,结账走出大门以后,下一个顾客才能进入这个商铺选购衣服。显然这样做是不合理,只要商铺不打烊,进入商铺什么时候都是可以的,即使现在有顾客在试穿衣服,但是选择衣服这个事请别个顾客也是可以同时进行的,只有使用试衣间时需要同步。再回到我们的代码中分析,其实多个线程是允许同时进入这个方法的,我们需要同步的地方仅仅是在新建单例实例的时候,如果方法中还有其他代码,多个线程大可把其他代码执行完毕,到了要新建单实例时,再排队等候。下面我们结合代码在代码中注释解释上面的双层判断:(假设现在有A B C 三个线程同时要访问getInstance方法。并且A是第一个进入同步代码块的线程,B进入时A还未解锁(同步代码还未执行完毕),C进入时A已经解锁(同步代码已经执行完毕))

public static SharedPreferenceInstance getInstance() {        //当C进入方法,发现mInstance 不为null,就直接返回mInstance    if (mInstance == null) {        //B 进入时由于A还未创建完毕,发现mInstance为null 进入分支 发现下面代码 被锁了 就等待...        synchronized (SharedPreferenceInstance.class) {//这部分代码被A锁住        //当A创建完毕后B进入同步代码块 ,B又一次判断,发现mInstance已经不为null了,就直接解锁,返回mInstance            if (mInstance == null) {                 mInstance = new SharedPreferenceInstance();//A 正在执行创建...            }        }    }    return mInstance;    //通过以上的双层判断加同步锁就能保证实例唯一,并且可以保证线程并不需要都走同步代码块,提高了代码的执行效率。}

单例模式中的内存优化

首先我们分析一下饿汉式单例类,可以发现不管我们现在需不需要这个单例,程序在一开始就直接new 出了实例,这就会导致一个问题,浪费了内存空间。
我们再来看看懒汉式单例类,与饿汉式相比虽然一开始没有直接new出对象,但是却用到了线程同步降低了代码的执行效率。
那么,有没有既不浪费空间,又不会降低效率,并且还能保证单例性的方法呢?
答案肯定是有的,比如如下这样:

public class SharedPreferenceInstance {    private SharedPreferences mPreferences;    //常量key,用来储存或取出数据    private static final String MAIN_CONTENT = "MAIN_CONTENT";    private SharedPreferenceInstance() {        mPreferences = PreferenceManager.getDefaultSharedPreferences(MyApplication.getApp().getApplicationContext());    }    //外部获取单例方法    public static SharedPreferenceInstance getInstance() {        return Holder.mInstance;    }    // 静态内部类 持有单例对象    private static class Holder {        private static SharedPreferenceInstance mInstance = new SharedPreferenceInstance();    }    //保存    public void saveContent(String content) {        if (mPreferences == null) return;        mPreferences.edit().putString(MAIN_CONTENT, content).apply();    }    //读取    public String getContent() {        if (mPreferences == null) return null;        return mPreferences.getString(MAIN_CONTENT, null);    }}

分析:可以发现与之前相比我们增加了一个静态内部类Holder,该类仅有一个外部类的成员,并在声明的时候就初始化了。大家都知道,静态内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。当getInstance方法第一次被调用的时,Holder类也会被第一次读取,就会导致Holder类的装载与初始化,这时就会初始化Holder类的静态域,从而mInstance也会得到创建,由于静态域只会在类装载时初始化一次,并由虚拟机来保证它的线程安全,所以这种方式就巧妙的达到了我们之前的需求。

当然使用枚举还是更方便、简洁、高效的创建单例的方式。

原创粉丝点击