Android设计模式-单例模式
来源:互联网 发布:高铁隧道网络覆盖 编辑:程序博客网 时间:2024/06/05 19:48
单例模式介绍
单例模式是应用最广的模式之一。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需一个全局对象,这样有利于协调系统的整体行为。如在一个应用中,应当只有一个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<Staff> allStaffs = new ArrayList<Staff>(); 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设计模式---单例
- Android单例设计模式
- Android 设计模式 之 单例模式
- Android 设计模式 之 单例模式
- Android 设计模式之单例模式
- Android 设计模式 之 单例模式
- Android设计模式系列-单例模式
- Android 设计模式 之 单例模式
- Java(Android)设计模式-单例模式
- Android设计模式--单例模式
- android设计模式之单例模式
- Android设计模式系列-单例模式
- Android 设计模式 之 单例模式
- Android 设计模式之单例模式
- Android 设计模式之单例模式
- Android 设计模式之单例模式
- Android 设计模式 之 单例模式
- android设计模式-单例模式
- 开源 java CMS BBS
- java 输入生日 判断年龄 星期几 距今天数
- java会员管理系统(对象数组的使用)
- 析构函数与虚析构函数的用法,构造函数与析构函数调用情况
- 更新AS2.3.1后更新慢及更新好后出现Gradle sync failed: Failed to find Build Tools revision 26.0.1的解决办法
- Android设计模式-单例模式
- Spark简介
- jQuery设计思想--笔记
- 基于DHT11温湿度传感器的Android硬件访问服务的简单实现(二)
- 矩阵快速幂模板
- 霍夫变换——hough
- C# 可空类型(Nullable)
- MyEclipse项目导入idea
- 初学css--css的简单介绍