浅谈singleton单例模式

1、前言java

单例模式比较简单,能够说没有复杂的调用和接口的设计,就是一个简单的类,只是要求这个类只生成一个对象,不管何时都要保证这一点,所以只能生成一个实例的模式就是单例模式。设计模式

2、类的加载缓存

类的加载是经过类加载器(Classloader)完成的,它既能够是饿汉式加载类,也能够是懒汉式加载,这跟不一样的JVM实现有关。加载完类后,类的初始化就会发生,若是是对一个类的主动使用就会初始化对象,对类的被动使用不会对类进行初始化,好比final修饰的静态变量若是能在编译时就肯定变量的取值,会被当作常量,做为对一个类的被动使用不会致使类的初始化。如下状况类被初始化:安全

类初始化的一些规则:多线程

  1. 类从顶到底的顺序初始化,因此声明在顶部的字段遭遇底部的字段初始化;
  2. 超类早于子类和衍生类的初始化;
  3. 若是类的初始化是因为访问静态域而触发,那么只能声明静态域的类才被初始化,而不会触发超类的初始化或者子类的初始化,即便静态域被子类或子接口或者它的实现类锁引用;
  4. 接口初始化不会致使父接口的初始化;
  5. 静态域的初始化时在类的静态初始化期间,非静态域的初始化是在类的实例建立期间,这意味着静态域初始化在非静态域以前;
  6. 非静态域经过构造器初始化,子类在作任何初始化以前构造器会先调用父类的构造器,它保证了父类非静态或实例变量初始化早于子类;

3、单例模式的特色性能

单例模式有如下特色:spa

  1. 单例类只能有一个实例。
  2. 单例类必须本身建立本身的惟一实例。
  3. 单例类必须给全部其余对象提供这一实例。

目的:单例模式确保某个类只有一个实例,并且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具备资源管理器的功能。每台计算机能够有若干个打印机,但只能有一个打印服务,以免两个打印做业同时输出到打印机中。每台计算机能够有若干通讯端口,系统应当集中管理这些通讯端口,以免一个通讯端口同时被两个请求同时调用。总之,选择单例模式就是为了不不一致状态,避免政出多头。.net

4、饿汉式单例线程

public class Singleton {
  private Singleton() {}
  private static final Singleton single = new Singleton();
  //静态工厂方法 
  public static Singleton getInstance() {
      return single;
  }
}

由于这自己就是static修饰的方法,因此是在类加载的时候被建立,后期不会再改变,因此线程是安全的。设计

5、懒汉式单例

public class SingletonTest {
    public static SingletonTest singleton = null;
    public static SingletonTest getInstance(){
        if(singleton == null){
            singleton = new SingletonTest();
            System.out.println("建立一次");
        }
        return singleton;
    }
    public void show(){
        System.out.println("我是江疏影");
    }

    public static void main(String[] args) {
        SingletonTest singleton = SingletonTest.getInstance();
        SingletonTest singleton1 = SingletonTest.getInstance();
        singleton.show();
        singleton1.show();
        if(singleton==singleton1){
            System.out.println("该对象的字符串表示形式:");
            System.out.println("singleton :"+singleton.toString());
            System.out.println("singleton1:"+singleton1.toString());
        }
    }
}

懒汉式方法老是会出现这样或那样的问题的,由于考虑到了多线程机制,实现起来比较麻烦,而且还会出现问题,就算是使用了必定的解救办法(同步、加锁、双重判断)的办法,性能仍是被损耗了,所以懒汉式方法的弊端很是大。

6、双检锁/双重校验锁

描述:采用双锁机制,安全且在多线程状况下能保持高性能。多线程安全

package designMode;

public class Singleton {
    private volatile static Singleton singleton;
    public static synchronized Singleton getSingleton(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

或者使用以下方式,双重判断,第二次判断就是防止已经有一个对象产生了,所以也能够达到相应的目的。

package designMode;

public class Singleton {
    private volatile static Singleton singleton;
    public static Singleton getSingleton(){
        if(singleton==null){
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

这里的两次判断,第一判断:效率,第二判断:避免同步。之因此这样是由于避免加锁后,再次加锁。大大加强了执行效率。

7、枚举实现单例

一、枚举单例(Enum Singleton)在Effective Java一书中提到,由于其功能完善,使用简洁,无偿的提供了序列化机制,在面对复杂的序列化或者反射攻击时依然能够绝对防止屡次实例化等优势,被做者所推崇。

二、枚举单例写法简单

如上文提到的DCL(double checked locking),实在是优势麻烦,枚举单例相对简单的多。下面这段代码就是声明枚举实例的一般作法,它可能还包含实例变量和实例方法,枚举单例是线程安全的。

public enum  DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum(){
        connection = new DBConnection();
    }
    public DBConnection getConnection(){
        return connection;
    }
}
public class Test {
    public static void main(String[] args) {
        DBConnection conn1 = DataSourceEnum.DATASOURCE.getConnection();
        DBConnection conn2 = DataSourceEnum.DATASOURCE.getConnection();
        System.out.println(conn1 == conn2);
    }
}

8、静态内部类实现单例

package designMode.singleton;

public class Singleton {
    private static class SingleTonHoler{
        private static Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(){
        return SingleTonHoler.INSTANCE;
    }
}

一、静态内部类实现单例的优势是:外部类加载时并不须要当即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不须要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会致使虚拟机加载SingleTonHoler类,这种方法不只能确保线程安全,也能保证单例的惟一性,同时也延迟了单例的实例化。

二、当getInstance()方法被调用时,SingleTonHoler才在SingleTon的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被建立,而后再被getInstance()方法返回出去,这点同饿汉模式。那么INSTANCE在建立过程当中又是如何保证线程安全的呢?在《深刻理解java虚拟机》中,有这么一句话:

虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,若是多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其它线程都须要阻塞等待,直到活动线程执行<clinit>()方法完毕。若是一个类的<clinit>()方法中有耗时很长的操做,就有可能形成多个进程阻塞(须要注意的是,其它线程虽然会被阻塞,但若是执行<clinit>()方法后,其它线程唤醒以后不会再次进入<clinit>()方法。同一个加载器下,一个类型只会初始化一次。)在实际应用中,这种阻塞每每是隐蔽的。

<clinit>()方法简介:

一、先理解类初始化阶段的含义:该阶段负责为类变量赋予正确的初始值,是一个类或接口被首次使用前的最后一项工做。

二、<clinit>()方法的执行时期:类初始化阶段(该方法只能被JVM调用,专门承担类变量的初始化工做)。

三、<clinit>()方法的内容:全部的类变量初始化语句和类型的静态初始化器。

四、类的初始化时机:

  • 首次建立某个类的新实例时(new,反射,克隆,反序列化);
  • 首次调用某个类的静态方法时;
  • 首次使用某个类或接口的静态字段或对该字段(final字段除外)赋值时;
  • 首次调用java的某些反射方法时;
  • 首次初始化某个类的子类时;
  • 首次在虚拟机启动时某个含有main()方法的那个启动类。

五、注意:并不是全部的类都拥有一个<clinit>()方法,知足下列条件之一的类不会拥有<clinit>()方法:

  • 该类没有声明任何类变量,也没有静态初始化语句;
  • 该类声明了类变量,但没有明确使用类变量初始化语句或静态初始化语句初始化;
  • 该类仅包含静态final变量的类变量初始化语句,而且类变量初始化语句是编译时常量表达式。

 

故而,能够看出INSTANCE在建立过程当中是线程安全的,因此说静态内部类形式的单例可保证线程安全,也能保证单例的惟一性,同时也延迟了单例的实例化。

那么,是否是能够说静态内部类实现单例模式就是完美的了呢?其实否则,静态内部类实现单例也有一个致命的缺点,就是传参的问题,因为静态内部类的形式建立单例,故而没法传递参数进去,例如Contxt这种参数,因此咱们建立单例时,能够根据实际状况,进行斟酌。

9、总结

在这个单例模式中,我但愿你们不要只知道单例的思想,更要知道类的加载和初始化时机,以及多线程的机制,我想这才是真正有意义的呢。枚举单例有序列化和线程安全的保证,并且实现简单,是实现单例最好的方式。

 

素小暖讲设计模式@目录

相关文章
相关标签/搜索