Android设计模式-单例模式
Posted vanpersie_9987
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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<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@5e8fce95
Obj : com.android.dp.book.chapter2.company.CEO@5e8fce95
Obj : com.android.dp.book.chapter2.company.VP@3343c8b3
Obj : com.android.dp.book.chapter2.company.VP@222d2a10
Obj : com.android.dp.book.chapter2.company.Staff@1aa8c488
Obj : 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设计模式-单例模式的主要内容,如果未能解决你的问题,请参考以下文章