(一)什么是单例模式
在程序中,每new() 一个对象,就会有一个对象实例生成。有时候在程序中,需要有一个在完整运行状态下只需要生成一个的实例,我们把这种实例称为单例。 抽象到设计模式中,这种只生成一个实例的模式就是单例模式(Singleton)。
(二)单例模式的实现
单例模式的实现有很多种方式,归根到底是保证new Class()的操作只做一次,在大多数的实现上,会将需要实现单例的类的构造方法改为private修饰,使得无法通过new命令创建对象实例。
(三)单例模式的代码实现
代码模式有很多种实现方式,主要有饿汉式、懒汉式以及有点特殊的单例注册表(Spring的实现方式)。下面会针对上面的这三种方式各自给出一种实现方式。
3.1 饿汉式
饿汉式简单来说就是在项目启动时就将对象实例创建出来,可以用来做需要执行一次的操作:
publicclassHungrySingleton{publicstaticfinalHungrySingletonINSTANCE;static{INSTANCE=newHungrySingleton();}privateHungrySingleton(){}}
调用时只需要直接调用INSTANCE变量就行:
HungrySingletoninstance=HungrySingleton.INSTANCE;
静态代码块饿汉式适合从外部文件中获取数据时使用,我在项目下新建一个info.properties,里面包含一条内容info=hello。饿汉式单例代码如下:
publicclassHungrySingleton2{publicstaticfinalHungrySingleton2INSTANCE;privateStringinfo;static{Propertiesproperties=newProperties();try{properties.load(HungrySingleton2.class.getClassLoader().getResourceAsStream("info.properties"));}catch(IOExceptione){e.printStackTrace();}INSTANCE=newHungrySingleton2(properties.getProperty("info"));}privateHungrySingleton2(Stringinfo){this.info=info;}publicStringgetInfo(){returninfo;}publicvoidsetInfo(Stringinfo){this.info=info;}@OverridepublicStringtoString(){return"Singleton3{"+"info='"+info+'\''+'}';}publicstaticvoidmain(String[]args){HungrySingleton2instance=HungrySingleton2.INSTANCE;System.out.println(instance.getInfo());}}
3.2 懒汉式
懒汉式是指当第一次调用时才会生成这个实例,后面再调用就只会使用之前生成的实例。懒汉式是实际编写单例模式代码时用的比较多的方案,下面给出一段十分经典的懒汉式单例模式代码案例:
publicclassLazySingleton{privatestaticvolatileLazySingletonInstance;privateLazySingleton(){};publicstaticLazySingletongetInstance(){if(Instance==null){synchronized(LazySingleton.class){if(Instance==null){Instance=newLazySingleton();}}}returnInstance;}}
在面试中,往往这道题能引出volatile和synchorized这两个知识点。
3.3 单例注册表
我看网上很少有人介绍这种单例模式,单例注册表是Spring中Bean单例的核心实现方案。可以通过一个ConcurrentHashMap存储Bean对象,保证Bean名称唯一的情况下也能保证线程安全。下面是单例注册表的简单实现:
publicclassRegSingleton{privatefinalstaticMap<String,Object>singletonObjects=newConcurrentHashMap<>(16);privateRegSingleton(){}publicstaticObjectgetInstance(StringclassName){if(StringUtils.isEmpty(className)){returnnull;}if(singletonObjects.get(className)==null){try{singletonObjects.put(className,Class.forName(className).newInstance());}catch(Exceptione){e.printStackTrace();}}returnsingletonObjects.get(className);}}
新建一个测试类:
publicclassUser{privateStringid;publicvoidsetId(Stringid){this.id=id;}publicStringgetId(){returnid;}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(!(oinstanceofUser))returnfalse;Useruser=(User)o;returnObjects.equals(getId(),user.getId());}@OverridepublicinthashCode(){returnObjects.hash(getId());}}
最后测试单例是否生效:
publicclassMain{publicstaticvoidmain(String[]args){Useruser1=(User)RegSingleton.getInstance("com.javayz.singleton.User");Useruser2=(User)RegSingleton.getInstance("com.javayz.singleton.User");System.out.println(user1==user2);}}
(四)单例模式在Spring中的最佳实践
单例模式的最佳实践就是Spring中的Bean了,Spring中的Bean默认都是单例模式,Spring实现单例的方式就采用了单例注册表的方式。
首先在Bean工厂中,如果设置了Spring的Bean模式为单例模式,Spring就会通过getSingleton的方式去获取单例Bean对象。
接着就会进入到DefaultSingletonBeanRegistry类的getSingleton方法中,忽略掉其他代码,只看单例Bean生成相关代码
publicclassDefaultSingletonBeanRegistryextendsSimpleAliasRegistryimplementsSingletonBeanRegistry{/**Cacheofsingletonobjects:beannametobeaninstance.*/privatefinalMap<String,Object>singletonObjects=newConcurrentHashMap<>(256);publicObjectgetSingleton(StringbeanName,ObjectFactory<?>singletonFactory){//一系列处理操作}finally{if(recordSuppressedExceptions){this.suppressedExceptions=null;}afterSingletonCreation(beanName);}if(newSingleton){//如果实例对象不存在,注册到单例注册表中addSingleton(beanName,singletonObject);}}returnsingletonObject;}}}
对应的addSingleton方法就是将对象加入到单例注册表中:
(五)总结
至此,单例模式的内容差不多就结束了,结合源码看设计模式每次都有新收获。我是鱼仔,我们下期再见!
听说微信搜索《Java鱼仔》会变更强哦!
本文收录于github和gitee,里面有我完整的Java系列文章,学习或面试都可以看看哦