单例模式属于创建模式的一种,在Java中也是最重要、最简单、最常用的设计模式之一,当遇到以下情况时,单例模式就派上用场了。
再比如——对象。。。。。。。。。。。
一般一个类能否做成单例,最容易区别的地方就在于,这些类,在应用中如果有两个或者两个以上的实例会引起错误,又或者我换句话说,就是这些类,在整个应用中,同一时刻,有且只能有一种状态。
单例模式的实现方法主要有四种:饿汉式、懒汉式、工厂方法、枚举方法。前两者是比较常见的的实现方法,当然如果面试时能将后两项也完美阐述的话,说明你的水平可不是一般货色可比的..>.<..下面贴代码,主要阐述都在注释里。。
一、饿汉式
package Singleton; /* * 单例模式 * 应用场合:有些对象只需一个就可以了 * ①要求生成唯一序列号的环境 * ②在整个项目中需要一个共享访问点或共享数据 * ③需要定义大量的静态常量和静态方法(如工具类)的环境,可以使用单例 * ④创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源 * * 作用:保证整个应用程序中某个实例只有一个,并提供一个访问他的全局访问点 * 类型:饿汉模式,懒汉模式 * * */ public class Singleton { //饿汉模式:缺点是类实例化后不能添加逻辑 //1.将构造方法私有化,不允许外部直接创建对象 private Singleton(){ } //2.创建类的唯一实例 static 静态的可将其变为类的成员 private static final Singleton instance=new Singleton(); //3.提供一个用于获取实例的方法 public static Singleton getInstance(){ return instance; } }
二、懒汉式
package Singleton; /* * 懒汉模式:只声明实例,并没有实例化 * 但类加载时,并没有创建实例,而当用户获取时,开始做判断(是否为空),若为空,才创建实例 * 创建之后(第2、3、4···)再来获取时,由于实例已存在,就不会再创建实例了 * * */ public class Singleton2 { //懒汉模式 //1.将构造方法私有化,不允许外部直接创建对象 private Singleton2(){ } //2.声明类的唯一实例,使用private static 修饰 private static Singleton2 instance; //3.提供一个用于获取实例的方法,使用public static修饰 public static Singleton2 getInstance(){ if(instance==null){ //多线程下不安全 instance=new Singleton2(); } return instance; } }
三、工厂化方法,可以在创建实例过程中添加逻辑(主要针对懒汉式的缺点)
package Singleton; /** * 工厂化方法:可以在实例过程中添加逻辑 * */ public class Singleton3 { private Singleton3(){ } private static Singleton3 instance=new Singleton3(); public static Singleton3 getInstance(){ return instance; } }
四、枚举类方法(注意是 ~枚举 ~ 类 enum),最佳推荐,代码最简洁 **.**
package Singleton; /** * 枚举类方式:最佳实践,推荐 * effective java 推荐 * */ public enum Singleton4 { instance; }
五、对前四种单例方法验证一下,注意看实例创建的方式
package Singleton; /*懒汉模式和饿汉模式的区别: * 懒汉模式:加载类时比较快,但运行时获取对象的速度比较慢,线程不安全 * 饿汉模式:加载类时比较慢,但运行时获取对象的速度比较快(因对象早已创建好),线程安全 * * * */ public class TestSingleton { //饿汉模式 public static void main(String[] args) { System.out.println("饿汉模式"); Singleton s1=Singleton.getInstance(); Singleton s2=Singleton.getInstance(); if(s1==s2){ System.out.println("s1和s2是同一个实例"); }else{ System.out.println("s1和s2是同一个实例"); } //懒汉模式 System.out.println("懒汉模式"); Singleton2 s3=Singleton2.getInstance(); Singleton2 s4=Singleton2.getInstance(); if(s3==s4){ System.out.println("s3和s4是同一个实例"); }else{ System.out.println("s3和s4不是同一个实例"); } //工厂方法 System.out.println("工厂方法模式"); Singleton3 s5=Singleton3.getInstance(); Singleton3 s6=Singleton3.getInstance(); if(s5==s6){ System.out.println("s5和s6是同一个实例"); }else{ System.out.println("s5和s6不是同一个实例"); } //枚举 System.out.println("枚举"); Singleton4 s7=Singleton4.instance; Singleton4 s8=Singleton4.instance; if(s7==s8){ System.out.println("s7和s8是同一个实例"); }else{ System.out.println("s7和s8不是同一个实例"); } } }
运行结果:
六、针对最经典的懒汉模式易造成线程不安全的问题再多加一句废话
为什么说懒汉式是最经典的呢?因为饿汉模式这种方式最主要的缺点就是一旦我访问了Singleton的任何其他的静态域,就会造成实例的初始化,而事实是可能我们从始至终就没有使用这个实例,造成内存的浪费。
假设n个线程,当并发访问的时候,第一个调用getInstance方法的线程A,在判断完singleton是null的时候,线程A就进入了if块准备创造实例,但是同时另外一个线程B在线程A还未创造出实例之前,就又进行了singleton是否为null的判断,这时singleton依然为null,所以线程B也会进入if块去创造实例,这时问题就出来了,有两个线程都进入了if块去创造实例,结果就造成单例模式并非单例。