单例模式--单对象创建(创建型模式01)

    xiaoxiao2021-03-25  84

    什么是单例模式? 一个类只有一个实例存在,面向整个类。 如下代码:

    public class ImageCache{ public void getDownload(){} public void put(){} } public class Image{ public void ImageDownload(){ //第一次实例化ImageCache类 ImageCache imgCache = new ImageCache(); imgCache.getDownload(){} } public void ImageStore(){ //第二次实例化ImageCahce类 ImageCache imgCache = new ImageCache(); imgCache.put(){} } }

    如上代码,我们两次实例化了ImageCache类,这就浪费了不必要的资源,为什么这样说?想象一下,假如ImageCache类中有成千上万行代码,包含线程池、缓存系统、网络请求等。那多次实例化将耗费大量的系统资源。单例模式要求实例化一次且面向整个类,所以优化后代码这样写:

    public class Image{ /**实例化一次,面向整个类可用,在方法内实例化是只能在作用域内使用,就是方法内。所以其他地方不能用,故要在 *类的作用域内实例化,面向整个类都可用。 */ ImageCache imgCache = new ImageCache(); public void ImageDownload(){ imgCache.getDownload(){} } public void ImageStore(){ imgCache.put(){} } }

    这就是单例模式,其实我们没有接触设计模式之前就知道这样做了,所以设计模式是一种学习经验。


    单例模式进阶 单例模式要注意以下四点:

    构造函数一般为Private声明通过静态方法或枚举返回实例确保实例化对象的单一,尤其在多线程环境下确保实例化对象反序列化时不重构对象

    关于Private声明和构造方法 Private为私有的意思,也就是你声明的变量只有自己能用,其他类不可调用。

    public class ImageCache{ /** *public Imagecache(){}这就是构造方法:我们通过new ImageCache();就是执行这个方法来构造 *ImageCache对象的,你写或者不写这个无参函数,系统都会默认执行,当然如果你这样构造public *Imagecache(String string){}这就是自定义的有参构造,这时候不写public Imagecache(){}系统 *就认为你定义了有参构造函数,new的时候执行这个有参构造函数。 */ public ImageCache(){} //相关代码片 }

    用Private声明构造函数:

    public class ImageCache{ private ImageCache(){} //相关代码片 }

    这样写之后,你会发现当你在其他类实例化ImageCache imgCache = new ImageCache();的时候他报错,因为private声明的只能本类使用,其他类无法使用。这样做的目的是防止你调用ImageCache类的时候他自动初始化。


    1.懒汉模式:(用了才实例化,所以懒。。)

    public class ImageCache{ private static ImageCache instance; private ImageCache(){} public static (synchronized) ImageCache getInstnce(){ if(instance == null){ instance = new ImageCache(); } return instance; } }

    加入synchronized关键字意思是线程同步,在多线程间操作同一对象时只能有一个线程操作,不会同时操作而引起问题。不加只适用于单线程。加了效率低。


    2.饿汉模式(一开始使用类就实例化对象,所以饿。。)

    public class ImageCache{ private static ImageCache instance = new ImageCache(); private ImageCache(){} public static ImageCache getInstnce(){ return instance; } }

    这种方式不用加锁,执行效率高点,因为类加载时候他就实例化实例了,当然会浪费内存。


    3.双重锁校检模式

    public class ImageCache{ private ImageCache(){}//构造函数私有化 private volatile static ImageCache instance = null//声明静态对象ImageCache public static ImageCache getInstance(){//静态方法实例化对象 if(instance == null){//首次判空 synchronized(ImageCache.class){//同步类 if(instance == null){//二次判空 instance = new ImageCache();//实例化对象 } } } return instance; //返回对象 }

    这里进行了两次判空,作用是,synchronized同步需要消耗资源,如果在我们首次判断这个对象非空也就是实例化过的,就直接返回对象,跳过二次同步,想想一下如果没有外层判空每次我们是不是都要进行同步操作呢。这个同步尤其在多线程比较明显,它的作用就是让你的任务操作排队进行,不能同时操作。但是jvm允许执行乱序操作,所以他会出现1-2-3、1-3-2的顺序执行问题。所以官方给出了volatile关键字来解决这个问题,但是还是会影响点性能。优点是保证在多线程下保持高性能。


    4.静态内部类来实现单例模式

    public class ImageCache{ private ImageCache(){} public static ImageCache getInstance(){ return ImageCacheHolder.mInstance; } /** *静态内部类 */ private static class ImageCacheHolder{ private static final ImageCache mInstance = new ImageCache(); } }

    这样设计当我们导入ImageCache类的时候,规避的问题有:调用类ImageCache的时候不会实例化,只有调用getInstance()方法才会实例化。如果instance操作耗费资源,这里实现延迟加载。但这种方式只适用于静态域。

    ImageCache imgCache =ImageCache.getInstance(); //静态方法在ImageCache已经发生实例化、静态内部类并没有只调用Holder类getInstance(); //静态内部类在。getInstance()实例化

    5.枚举实现单例模式 实现单例模式的最佳方法,但还未被广泛采用。

    public enum ImageCache{ INSTANCE; }

    实例调用这样写:

    ImageCache.INSTANCE;

    支持自动序列化,防止多次实例化,默认线程安全。规避多线程问题。


    1-4在反序列化时候会重新构造函数,枚举规避这种情况。要遇到需要加入如下方法返回实例,而不是新建:

    private Object readResolve() throws ObjectStreamException{ return mInstance; }

    无论我们以哪种方式实现单例模式,原理都是将构造函数私有化,通过静态方法获取唯一实例,再考虑线程安全和资源利用率等情况。具体取用哪一种,取决于项目本身,综合情况选择最佳的方式。

    单例模式的优缺点

    在内存中只有一个实例,减少内存开支。避免资源多重占用,比如对同一文件同时执行写操作。缺点是单例模式没有借口,扩展困难,除了修改代码,无法实现,这样就违背了开闭原则。

    转载请注明原文地址: https://ju.6miu.com/read-21430.html

    最新回复(0)