介绍

采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例。并且该类只提供一个取得对象实例的方法。比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象,它并不是轻量级的,所以一个项目通常只需要一个SessionFactory就够了,这就需要用到单例模式。

单例模式的分类

  • 饿汉式:静态常量 ✅
  • 饿汉式:静态代码快
  • 懒汉式:线程不安全
  • 懒汉式:线程安全,同步方法
  • 懒汉式:线程安全,同步代码块
  • 双重检查 ✅
  • 静态内部类 ✅
  • 枚举 ✅

饿汉式(静态常量)

创建步骤大概如下:

  • 构造器私有化,这样是防止new
  • 类的内部创建对象
  • 向外暴露一个静态的公共方法:getInstance
public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2); //true
    }
}


// 饿汉式-静态常量创建单例模式
class Singleton{
    //1.构造方法私有化
    private Singleton(){}

    //2.本类内部创建一个对象实例
    private static final Singleton instance = new Singleton();

    //3.对外提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}

优点:比较简单,在类装载的时候就创建了实例,避免了线程同步问题。

缺点:在类加载的时候就完成了实例化,没有达到懒加载的效果,如果从始至终都没有用这个实例,那就回造成内存浪费

饿汉式(静态代码块)

这种写法跟上面的区别就是将对象的实例化放在了静态代码块中,优缺点也跟上面一样,在类加载的时候经实例化了对象,可能会造成内存的浪费。

public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2); //true
    }
}


// 饿汉式-静态代码块创建单例模式
class Singleton{
    //1.构造方法私有化
    private Singleton(){}

    //2.静态代码块中实现实例的创建
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    //3.对外提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}

懒汉式(线程不安全,不推荐)

步骤大概如下:

  • 创建一个私有静态属性,先不赋值
  • 私有化构造方法,防止new
  • 提供一个静态公有方法,当使用到这个方法的时候才去new
// 懒汉式-线程不安全
class Singleton{
    //1.创建属性
    private static Singleton instance;

    //2.构造方法私有化
    private Singleton(){}


    //3.对外提供一个公有的静态方法,用到时才去实例化
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优点:能起到懒加载的效果

缺点:只能在单线程的情况下使用

如果在多线程的情况下,一个线程进入了if判断里面,还没来得及往下执行,另一个线程也进入到了这个判断中,这样就会产生两个实例,如果有多个线程就可能会产生多个实例,所以这就是简单懒汉式的问题,会出现线程不安全。在实际开发中,不能使用这种方式。

懒汉式(线程安全,同步方法,效率低不推荐)

只需要在getInstance方法上加一个synchronized关键字就可以了

// 懒汉式-线程安全,在方法上加synchronized
class Singleton{
    //1.创建属性
    private static Singleton instance;

    //2.构造方法私有化
    private Singleton(){}


    //3.对外提供一个公有的静态方法,用到时才去实例化
    public synchronized static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优点:解决了线程安全问题

缺点:效率太低,其实我们只需要在第一次实例化对象的时候让他线程安全就可以了。创建完对象后,无论多少线程再访问getInstance方法都不会出问题,如果在方法级别上加同步,这样就会导致效率太低。

懒汉式(线程安全,同步代码块,错误)

// 懒汉式-线程安全,同步代码块实现
class Singleton{
    //1.创建属性
    private static Singleton instance;

    //2.构造方法私有化
    private Singleton(){}


    //3.对外提供一个公有的静态方法,用到时才去实例化
    public  static Singleton getInstance(){
        if(instance == null){
          	//同步代码块,只有第一次创建实例时才起作用
            synchronized (Singleton.class){
                instance = new Singleton();
            }
        }
        return instance;
    }
}

这种方式其实是错误的,不能用这样常见单例。它并不能起到线程同步的作用,跟第三种情形一致,如果有两个线程都进入到了if里面,第一个创建完之后释放锁,第二个拿到锁之后还会去创建一个实例对象,同样会产生两个对象。

懒汉式(双重检查,推荐)

// 懒汉式-线程安全,双重检查
class Singleton{
    //1.创建属性,在属性上加volatile
    private static volatile Singleton instance;

    //2.构造方法私有化
    private Singleton(){}
    
    //3.对外提供一个公有的静态方法,用到时才去实例化
    public  static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile关键字的作用就是,当一个线程修改了变量之后,另一个线程能知道,也就是使一个变量在多个线程可见。当两个线程同时进入到第一个if中以后,一个线程等待另一个线程创建完实例后释放锁,等它拿到锁的时候会再次进行判断,发现已经有了实例,所以就不会再进行实例的创建了。

懒汉式(静态内部类,推荐)

静态内部类的特点就是:

  • 当他的外部类被装载的时候,内部类是不会被装载的。
  • 当静态内部类被调用的时候,内部类会被装载,而且只能被装载一次。
  • 装载的时候,线程是安全的,因为JVM在装载类的时候是线程安全的
// 懒汉式-静态内部类
class Singleton{
    //1.构造方法私有化
    private Singleton(){}

    //2.创建静态内部类
    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

    //3.公有静态方法,获取实例
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

优点:

  • 采用类装载的机制来保证初始化实例时只有一个线程,避免了线程不安全
  • 采用静态内部类实现延迟加载,效率比较高

枚举(推荐)

public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.INSTANCE;
        Singleton singleton2 = Singleton.INSTANCE;
        System.out.println(singleton1 == singleton2); //true
    }
}


// 枚举法
enum Singleton{
    INSTANCE;   //属性
    public void sayOk(){
        System.out.println("ok");
    }
}

借助JDK1.5推出的枚举,也可以完成单例模式。不仅能解决线程安全问题,而且还能反序列化重新创建新的对象。

使用场景

  • 需要频繁创建销毁的对象
  • 创建耗时过多、耗费资源过多的对象
  • 经常用到的对象
  • 工具类对象
  • 经常访问数据库、文件的对象(数据源、Session工厂等)

注意事项

  • 单例模式保证了系统内存中只有一个该类的对象,节省了系统资源
  • Java中的Runtime就是用了饿汉式的单例模式
  • 使用单例模式的时候,要将构造方法私有化,使用相应的方法来获取对象