单例模式

定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

类型:创建类模式

单例模式有以下几个要素:

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

      单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例

      饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;而懒汉式在调用取得实例方法的时候才会实例化对象。代码如下:

      饿汉式单例

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return singleton;
    }
}
View Code

      懒汉式单例

public class Singleton {
    private static Singleton singleton;
    private Singleton(){}
    
    public static synchronized Singleton getInstance(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}
View Code

     双锁单例

public class SingletonClass { 
  private static volatile SingletonClass instance = null; 
  public static SingletonClass getInstance() { 
    if (instance == null) //先检查实例是否存在,如果不存在才进入下面的同步块
       { //同步块,线程安全的创建实例
      synchronized (SingletonClass.class) { 
        if (instance == null) { 
//这里要进一步检查是否为null,因为两个线程都可能穿过第一个判断为空的条件,
 当一个线程创建好,另一个线程苏醒过来,再次进入同步块,需要进一步进行判定!
          instance = new SingletonClass(); 
         } 
      } 
    } 
    return instance; 
  } 
  private SingletonClass() { } 
}
View Code

单例模式的优点:

  • 在内存中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用
  • 可以全局访问。

适用场景:由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。我总结了一下我所知道的适合使用单例模式的场景:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。
  • 所有要求只有一个对象的场景
  • 单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理

单例模式注意事项:

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。

在一个jvm中会出现多个单例吗

      在分布式系统、多个类加载器、以及序列化的的情况下,会产生多个单例,这一点是无庸置疑的。那么在同一个jvm中,会不会产生单例呢?

      使用单例提供的getInstance()方法只能得到同一个单例,除非是使用反射方式,将会得到新的单例。代码如下

Class c = Class.forName(Singleton.class.getName());
Constructor ct = c.getDeclaredConstructor();
ct.setAccessible(true);
Singleton singleton = (Singleton)ct.newInstance();
View Code

      这样,每次运行都会产生新的单例对象。所以运用单例模式时,一定注意不要使用反射产生新的单例对象

单例类可以被继承吗?

        饿汉式单例和懒汉式单例由于构造方法是private的,所以他们都是不可继承的,但是其他很多单例模式是可以继承的,例如登记式单例。

在某些情况下,JVM已经隐含的为您执行了同步,这些情况下就不用自己再来进行同步控制了。
 *   这些情况包括:
 *   (1)由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
 *   (2)访问final字段时
 *   (3)在创建线程之前创建对象时
 *   (4)线程可以看见将要处理的对象时
解决方案的思路
 *    要想简单的实现线程安全,可以采用静态初始化器的方式,可以由JVM来保证线程的
 *   安全性。比如前面的饿汉式实现方式。但是这样一来,不是会浪费一定的空间吗?因为这种
 *   实现方式,会在类装载的时候就初始化对象,不管你需不需要。
 *      如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的
 *   方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。只要不使用到这个类级内部类,
 *   那就不会创建对象实例,从而同步实现延迟加载和线程安全。

    静态初始化器是由static修饰的一对花括号“{}”括起来的语句组。它的作用和构造方法都是用来完成初始化工作的,静态初始化器与构造方法有以下几点根本不同。

     a、构造方法是对每一个新创建的对象初始化,而静态方法是对类自身进行初始化。

     b、构造方法是在new运算符创建新对象的时候由系统执行,而静态初始化器一般不能由程序调用,它是在所属类被加载入内存时由系统调用执行的。

     c、用new运算符创建多少个新的对象,构造方法就被调用那个多少次,但是静态初始化器则是在被类加载入内存时只执行一次,与创建多少个对象无关。

     如果有多个静态初始化器,则它们在类的初始化时会依次执行。

     类是在第一次被使用的时候才被装载,而不是在程序启动时就装载程序中得所有可能用到的类。

     静态初始化器的作用是对整个类完成初始化操作,包括给static成员变量赋初值,它在系统向内存加载时自动完成。

public class SingletonClass { 
  private static class SingletonClassInstance { 
    private static  SingletonClass instance = new SingletonClass(); 
  } 
  public static SingletonClass getInstance() { 
    return SingletonClassInstance.instance; 
  } 
  private SingletonClass() { } 
} 

       在这段代码中,因为SingletonClass没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonClassInstance类,这个类有一个static的SingletonClass实例,因此需要调用SingletonClass的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次

类级的内部类,就是静态的成员式内部类,该内部类的实例与外部类的实例,没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

      在多线程环境下,单例对象的同步问题主要体现在两个方面,单例对象的初始化单例对象的属性更新

a. 单例对象的属性(或成员变量)的获取,是通过单例对象的初始化实现的。也就是说,在单例对象初始化时,会从文件或数据库中读取最新的配置信息。

b. 其他对象不能直接改变单例对象的属性,单例对象属性的变化来源于配置文件或配置数据库数据的变化。

     

     上述的单例模式:序列化可能会破坏单例模式,比较每次反序列化一个序列化的对象实例时都会创建一个新的实例,解决方案如下:

public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     

   protected Singleton() {     
   }  

   //反序列时直接返回当前INSTANCE
   private Object readResolve() {     
            return INSTANCE;     
      }    
}   

      使用反射强行调用私有构造器,解决方式可以修改构造器,让它在创建第二个实例的时候抛异常,如下:

public static Singleton INSTANCE = new Singleton();     
private static volatile  boolean  flag = true;
private Singleton(){
    if(flag){
    flag = false;   
    }else{
        throw new RuntimeException("The instance  already exists !");
    }
}

 

     单例的枚举:功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点。

     实现:

public enum Singleton {
    INSTANCE;
    private Singleton() {}
}

     用一个枚举实现单个数据源例子来简单验证一下:

     声明一个枚举,用于获取数据库连接。

public enum DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum() {
        connection new DBConnection();
    }
    public DBConnection getConnection() {
        return connection;
    }
}  

    模拟数据库连接

public class DBConnection {} 

测试通过枚举获取的实例是否相同:

 public class Main {
    public static void main(String[] args) {
        DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();
        DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();
        System.out.println(con1 == con2);
    }
}
View Code

 

参考

     https://blog.csdn.net/zhengzhb/article/details/7331369

     https://blog.csdn.net/gavin_dyson/article/details/70832185

posted on 2018-09-21 14:58  溪水静幽  阅读(133)  评论(0)    收藏  举报