Java 单例模式几种实现的差别

    xiaoxiao2021-03-26  29

    单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    思路是通过将该类的构造方法设为private,那么在其他类中不能直接实例化该类。那么为了得到该类的实例,需要有public的static方法来返回该类的实例。(之所以static是为了可以直接用类名来调用该方法,不然的话没有该类的实例,无法调用该类的非static方法)

    单例模式有以下特点:   1、单例类只能有一个实例。   2、单例类必须自己创建自己的唯一实例。   3、单例类必须给所有其他对象提供这一实例。

    单例模式主要有以下三种实现方式:懒汉式单例、饿汉式单例、登记式单例。下面来看代码示例:

    一、懒汉式单例

    [java]  view plain  copy //懒汉式单例类.在第一次调用的时候实例化自己    public class Singleton {       private Singleton() {}       private static Singleton single=null;       //静态工厂方法        public static Singleton getInstance() {            if (single == null) {                  single = new Singleton();            }             return single;       }   }  

    懒汉式其实是一种比较形象的称谓。既然懒,那么在创建对象实例的时候就不着急。会一直等到马上要使用对象实例的时候才会创建,懒人嘛,总是推脱不开的时候才会真正去执行工作,因此在装载对象的时候不创建对象实例。懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。

    二、饿汉式单例

    [java]  view plain  copy //饿汉式单例类.在类初始化时,已经自行实例化    public class Singleton1 {       private Singleton1() {}       private static final Singleton1 single = new Singleton1();       //静态工厂方法        public static Singleton1 getInstance() {           return single;       }   }  

    饿汉式也是一种比较形象的称谓。既然饿,那么在创建对象实例的时候就比较着急,饿了嘛,于是在装载类的时候就创建对象实例。饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。

    三、登记式单例

    [java]  view plain  copy //类似Spring里面的方法,将类名注册,下次从里面直接获取。   public class Singleton3 {       private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();       static{           Singleton3 single = new Singleton3();           map.put(single.getClass().getName(), single);       }       //保护的默认构造子       protected Singleton3(){}       //静态工厂方法,返还此类惟一的实例       public static Singleton3 getInstance(String name) {           if(name == null) {               name = Singleton3.class.getName();               System.out.println("name == null"+"--->name="+name);           }           if(map.get(name) == null) {               try {                   map.put(name, (Singleton3) Class.forName(name).newInstance());               } catch (InstantiationException e) {                   e.printStackTrace();               } catch (IllegalAccessException e) {                   e.printStackTrace();               } catch (ClassNotFoundException e) {                   e.printStackTrace();               }           }           return map.get(name);       }       //一个示意性的商业方法       public String about() {               return "Hello, I am RegSingleton.";           }           public static void main(String[] args) {           Singleton3 single3 = Singleton3.getInstance(null);           System.out.println(single3.about());       }   }     登记式单例 实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。  

    线程安全问题

    上文中懒汉式单例的代码是线程不安全的。为了线程安全,可以采用以下方法:

    1、在getInstance方法上加synchronized

    [java]  view plain  copy public static synchronized Singleton getInstance() {            if (single == null) {                  single = new Singleton();            }             return single;   }   2、双重检查锁定

    [java]  view plain  copy public static Singleton getInstance() {           if (singleton == null) {                 synchronized (Singleton.class) {                    if (singleton == null) {                       singleton = new Singleton();                   }                 }             }             return singleton;        }   3、静态内部类

    [java]  view plain  copy public class Singleton {              private Singleton(){}       /**       *    类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例       *    没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。       */       private static class SingletonHolder{           /**           * 静态初始化器,由JVM来保证线程安全           */           private static Singleton instance = new Singleton();       }              public static Singleton getInstance(){           return SingletonHolder.instance;       }   }  

    当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

    这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

    另外就静态内部类中的三个static说明如下:

    [java]  view plain  copy public static Singleton getInstance(){   return SingletonHolder.instance;}  

    中的static是为了可以用类名直接调用getInstance方法,所以必须为static。那么该方法内的成员变量也必须为static,否则那个成员变量就要用类的实例来访问了,这显然和通过类直接访问不相符。

    [java]  view plain  copy private static Singleton instance = new Singleton();  

    中的static是因为instance必须为static才可以在getInstance方法中出现。

    [java]  view plain  copy public static Singleton getInstance()  

    中的static是因为若内部类中出现静态成员变量,则该内部类必为静态内部类。

    最后来看一个单例模式应用的例子有助于理解:

    [java]  view plain  copy public class TMain{       public static void main(String[] args){           Singleton s1=Singleton.getInstance();           s1.setName("libai");           Singleton s2=Singleton.getInstance();           s2.setName("dufu");           System.out.println(s1.getName());           System.out.println(s2.getName());           if(s1==s2){               System.out.println("是同一个对象");           }else{               System.out.println("不是同一个对象");           }       }   }      class Singleton{       /* 私有构造方法,防止被实例化 */         private Singleton() {         }         private String name;       public void setName(String s){           this.name=s;       }       public String getName(){           return this.name;       }              /*饿汉式单例*/       private static final Singleton instance = new Singleton();                   /* 获取实例 */         public static Singleton getInstance() {             //return new Singleton();           return instance;        }            /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */         public Object readResolve() {             return getInstance();         }     }  

    输出为:

    [java]  view plain  copy dufu   dufu   是同一个对象   原文链接:http://blog.csdn.net/sinat_26888717/article/details/48226365

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

    最新回复(0)