单例模式
创建型模式,单例类有且仅有一个对象。并且对唯一对象提供全局访问点,不需要实例化该类的对象就可以实现。
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
应用场景
- 需要生成唯一序列的环境(计数器、打开回收站的窗口、多线程的线程池等)。
- 需要频繁实例化然后销毁的对象。
- 创建对象耗时/耗资源过多,但是经常用到的对象。
- 方便资源互相通信的环境。
优点
- 在内存中只有一个对象,节省内存空间。
- 避免频繁地创建销毁对象,可以提高性能。
- 避免对共享资源的多重占用,简化访问。
- 为整个系统提供一个全局访问点。
缺点
- 不适用于变化频繁的对象。
- 滥用单例带来的负面问题:为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而导致连接池溢出。
- 如果实例化的对象长时间不使用可能会被回收,会造成对象状态的丢失。
实现
两个步骤:
- 将类的构造方法定义为私有方法,其他类只能通过该类提供的静态方法来得到该类的唯一实例。
- 在该类内提供一个静态方法,当我们调用这个方法时,如果类保持的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。
饿汉式与懒汉式
饿汉式是在类创建时就进行实例化,饿汉式是指在需要的时候再创建类的实例化。
- 懒汉式,线程不安全
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance()
{
if(instance == null)
{
instance == new Singleton();
}
return instance;
}
}
不支持多线程,没有加锁synchronized,严格意义上来说不算单例模式。
- 懒汉式,线程安全
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance()
{
if(instance == null)
{
instance = new Singleton;
}
return instance;
}
}
支持多线程,但加锁会影响效率。(getInstance()的性能对应用程序不是很关键,因为使用不频繁)
- 饿汉式
public class Singleton{
private class Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
基于classloader机制避免了多线程的同步问题,但类在加载时就初始化,浪费内存。
classloader机制
classloader是用来加载class的,负责将class的字节码转换成内存形式的class对象。字节码可以来自磁盘文件* .class,也可以是jar包里的* .class,也可以来自远程服务器提供的字节流。JVM运行并不是一次性加载所需要的全部类的,程序在运行时会逐渐遇到许多不认识的新类,此时就会调用classloader来加载这些类,加载完成后就会将class对象存在classloader里面。
JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。
load:使用指定的二进制名称来加载类,这个方法的默认实现按照以下顺序查找类: 调用findLoadedClass(String)方法检查这个类是否被加载过 使用父加载器调用loadClass(String)方法,如果父加载器为Null,类加载器装载虚拟机内置的加载器调用findClass(String)方法装载类, 如果,按照以上的步骤成功的找到对应的类,并且该方法接收的resolve参数的值为true,那么就调用resolveClass(Class)方法来处理类。 ClassLoader的子类最好覆盖findClass(String)而不是loadClass()方法。 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。
- 双重校验锁(DCL)
public class Singleton {
private volatile static Singleton singleton; //volatile是轻量级的同步
private Singleton (){}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。
- 静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
和双重校验锁功效一样,但是实现更加简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。同时实现了lazy loading(只有显式调用getInstance方法时才会被实例化)。
- 枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
比较少用,但它是实现单例模式的最佳方法,自动支持序列化机制,防止反序列化,不能通过反射创建新的对象。也能避免多线程同步问题。
总结:一般情况下建议使用第3种饿汉方式,明确要实现lazy loading时用第5种静态内部类方式,涉及到反序列化时用第6种枚举方式,有其他特殊需求可考虑使用第4种双重校验锁机制。
JVM规范严格定义了何时需要对类进行初始化:
- 通过new关键字、反射、clone、反序列化机制实例化对象时。
- 调用类的静态方法时。
- 使用类的静态字段或对其赋值时。
- 通过反射调用类的方法时。
- 初始化该类的子类时(初始化子类前其父类必须已经被初始化)。
- JVM启动时被标记为启动类的类(简单理解为具有main方法的类)。