指点成金-最美分享吧

登录

Android设计模式-单例模式

佚名 举报

篇首语:本文由小编为大家整理,主要介绍了Android设计模式-单例模式相关的知识,希望对你有一定的参考价值。

单例模式介绍

单例模式是应用最广的模式之一。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需一个全局对象,这样有利于协调系统的整体行为。如在一个应用中,应当只有一个ImageLoader实例,这个ImageLoader中又含有线程池、缓存系统、网络请求等,很消耗资源,所以,没有理由让它构造多个实例。这种不能自由构造对象的情况,就是单例模式的使用场景。

单例模式定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式的使用场景

确保某个类有且只有一个对象,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个需要消耗过多的资源过多,如访问IO和数据库等资源时,这时应考虑使用单例模式。

实现单例模式主要的关键点:

(1)构造函数不对外开放,一般为private;

(2)通过一个静态方法或枚举返回单例类对象;

(3)确保单例类的对象有且只有一个,尤其是在多线程情况下;

(4)确保单例类对象在反序列化时不会重新构建对象。

示例

下面以公司里的CEO为例来简单演示一下,一个公司可以有几个VP、无数个员工,但CEO只能有一个:

//员工public class Staff     public void work()     //副总裁public class VP extends Staff     @Override    public void work()     //CEO,恶汉单例模式public class CEO extends Staff     private static final CEO sCeo = new CEO();    //构造方法私有化    private CEO()         //公有静态方法,对外暴露获取单例对象的接口    public static CEO getCeo()         return sCEO;        @Override    public void work()         //管理VP    public class Company     private List allStaffs = new ArrayList();    public void addStaff(Staff per)         allStaffs.add(per);        public void showAllStaffs()         for(Staff per : allStaffs)             System.our.println("Obj : " + per.toString());            public class Test     public static void main(String[] args)         Company cp = new Company();        //对象只能通过getCEO函数获取        Staff ceo1 = CEO.getCeo();        Staff ceo2 = CEO.getCeo();        cp.addStaff(ceo1);        cp.addStaff(ceo2);        //通过new创建VP对象        Staff vp1 = new VP();        Staff vp2 = new VP();        //通过new创建Staff对象        Staff staff1 = new Staff();        Staff staff2 = new Staff();        Staff staff3 = new Staff();        cp.addStaff(vp1);        cp.addStaff(vp2);        cp.addStaff(staff1);        cp.addStaff(staff2);        cp.addStaff(staff3);        cp.showAllStaffs();    
//输出Obj : com.android.dp.book.chapter2.company.CEO@5e8fce95Obj : com.android.dp.book.chapter2.company.CEO@5e8fce95Obj : com.android.dp.book.chapter2.company.VP@3343c8b3Obj : com.android.dp.book.chapter2.company.VP@222d2a10Obj : com.android.dp.book.chapter2.company.Staff@1aa8c488Obj : com.android.dp.book.chapter2.company.Staff@22998b08

从上述代码看出,CEO类不能通过new的形式构造对象,只能通过CEO.getCeo()函数来获取,而这个CEO对象是静态对象,并且在声明的时候就已经初始化了(这便是恶汉模式,单例的实现方式之一),这保证了CEO对象的唯一性。

单例模式的其它实现方式

懒汉模式

懒汉模式是声明一个静态对象,并且在第一次调用静态方法时初始化,并返回实例,它与恶汉模式的区别是:恶汉模式在类加载的时候就实例化了对象,而懒汉模式在第一次使用该实例的时候才初始化。

下面是懒汉模式的实现:

public class Singleton     private static Singleton instance;    private Singleton()         public static synchronized Singleton getInstance()         if(instance == null)             instance = new Singleton();                return instance;    

静态方法getInstance()添加了synchronized关键字,它保证了在多线程的情形下也能实例唯一,但这也是懒汉模式存在的问题:每次调用getInstance()方法都会进行同步,消耗不必要的资源。

小结:

  • 恶汉模式:优点:线程安全;缺点:在类加载时实例化,无论是否使用该单例,都会实例化,会造成一定的资源浪费。

  • 懒汉模式:优点:只在第一次使用时才初始化,且线程安全;缺点:每次使用该实例时都会进行同步,效率较低。

双检锁(Double-Check-Lock)实现单例

双检锁模式是懒汉模式的升级版本,同时克服了懒汉模式和恶汉模式的缺点:

public class Singleton     private static Singleton sInstance = null;    private Singleton()         public void doSth()         System.out.println("do sth.");        public static Singleton getInstance()         if(sInstance == null)             synchronized(Singleton.class)                 if(sInstance == null)                     sInstance = new Singleton();                                            return sInstance;    

考虑一种极端的情况:在多线程情况下,如果有A、B两个线程同时调用getInstance()方法,A线程先进入第一个判空,发现实例为空,此时A线程挂起,B线程进入第一个判空条件,发现为空,此时B线程挂起,A线程执行,进入synchronized临界区,第二次判空,条件满足,实例初始化,临界区执行结束,A线程挂起,B线程执行,进入临界区,第二次判空时发现实例已被初始化,退出临界区,B线程挂起,A线程执行,返回实例,A线程结束,B线程执行,返回实例,B线程结束。

接下来再有线程调用getInstance时,只会进行第一个if判断,不会再进入synchronized临界区。

小结:

双检锁的优点:资源利用率高,第一次执行getInstance时才被实例化,只需同步一次。

双检锁的缺点:第一次加载时反应稍慢。

除非并发线程数很大,否则双检锁的模式一般都可以满足需求。

静态内部类实现单例模式

《Java并发编程实践》谈到了双检锁模式的问题:并发量很大时,双检锁模式不可靠。

而使用静态内部类的方式可以解决这个问题:

public class Singleton     private Singleton()         public static Singleton getInstance()         return SingletonHolder.sInstance;        private static class SingletonHolder         private static final Singleton sInstance = new Singleton();    

当第一次加载Singleton类时,不会初始化sInstance(因为加载Singleton类时,静态内部类SingletonHolder 并不会被一并加载),只有在第一次调用getInstance()方法时,静态内部类才会被初始化。 这种方式既保证了线程安全性,同时也能够保证单例对象的唯一性,而且延迟了单例的实例化,这是推荐的单例模式实现方式。

枚举单例

枚举单例是一种简单的单例实现方式:

public enum SingletonEnum     INSTANCE;    public void doSth()         System.out.println("do sth.");    

枚举也是一种类,它也可以有字段、方法,而且创建枚举类时是线程安全的,任何情况它都是一个单例。即便是在序列化反序列化时,枚举也能保证单例的正确性。

不管哪种方式实现单例模式,它们的核心原理都是将构造方法私有化,并且通过静态方法获取一个唯一的实例,在这个获取过程中,必须保证线程安全、防止反序列化导致重新生成实例对象等问题。

运用单例模式

ImageLoader是最为常用的Android开发工具之一,最著名的ImageLoader当属Universal-Image-Loader,它的大概使用流程为:

public void initImageLoader(Context context)     //1、使用Builder构建ImageLoader    ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)    //加载图片的线程数    .threadPriority(Thread.NORM_PRIORITY - 2//编码图像的大尺寸,将在内存中缓存先前解码图像的小尺寸    .denyCacheImageMultipleSizeInMemory()    //设置磁盘缓存文件名称    .discCacheFileNameGenerator(new Md5FileNameGenerator())    //设置加载显示图片队列进程    .tasksProcessingOrder(QueueProcessingType.LIFO)    .writeDebugLogs()    .build();    //2、使用配置对象初始化ImageLoader    ImageLoader.getInstance().init(config);    //3、加载图片    ImageLoader.getInstance().displayImage("图片Url", myImageView);

下面是一个自实现的ImageLoader:

public final class ImageLoader     //ImageLoader实例    private static ImageLoader sInstance;    //网络请求队列    private RequestQueue mImageQueue;    //缓存    private volatile BitmapChche = new MemoryCache()    //图片加载配置对象    private ImageLoaderConfig mConfig;    //私有的构造方法    private ImageLoader()         /**     * 获取ImageLoader单例,双检锁模式     * @return 单例对象     */     public static ImageLoadergetInstance()          if(sInstance == null)              synchronized(ImageLoader.class)                  if(sInstance == null)                      sInstance = new ImageLoader();                                                 /**      * 通过配置类初始化ImageLoader,设置线程数量、缓存策略、加载策略等      * @param config 配置对象      */      public void init(ImageLoaderConfig congig)           mConfig = config;          mCache = mConfig.bitmapCache;          checkConfig();          mImageQueue = new RequestQueue(mConfig.threadCount);          mImageQueue.start();            //代码策略      //加载图片接口      public void displayImage(ImageView imageView, String url)           displayImage(imageView, url, null, null);            public void displayImage(final ImageView imageView, final String url, final Displayconfig, final ImageListener listener)           BitmapRequest request = new BitmapRequest(imageView, url, config, listener);          request.displayConfig = request.displayConfig != null ? request.displayConfig : mConfig.displayConfig;          // 添加到队列中          mImageQueue.addRequest(request);            public void stop()           mImageQueue.stop();            //加载图片Listener, 加载完成后回调给客户端代码      public static interface ImageListener           public void onComplete(ImageView imageView, Bitmap bitmap, String url);       

总结

单例模式是运用频率很高模式,但是,由于在客户端没有高并发的情况,所以,选择哪种实现没有太大影响,即便如此,仍然推荐使用静态内部类和枚举的实现单例的方式。

  • 单例模式的优点:

(1)由于在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁创建销毁时。

(2)减少了内存开销,一个对象需要比较多的资源时,如读取配置、产生其他依赖对象时,可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式解决。

(3)单例模式可以避免对资源的多重占用。如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

(4)单例模式可以在系统设置全局的访问点,优化和共享资源。如,设计一个单例类,负责所有数据表的映射处理。

  • 缺点:

(1)单例模式一般没有借口,难以扩展。

(2)单例如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象Context最好是Application Context。

以上是关于Android设计模式-单例模式的主要内容,如果未能解决你的问题,请参考以下文章