一、单例设计模式

1、概念
  • 即采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
2、三要素
  • 私有的构造方法
  • 私有的静态属性指向实例
  • public static的 getInstance方法,返回静态属性绑定的实例

二、单例模式之懒汉式

1、概念
  • 顾名思义,懒汉式就是在需要使用实例的时候才创建实例,即程序第一次访问单例模式实例时才进行创建。

  • 示例:

public class Cat {
    private String name;
    private String age;

    //保证一个类只有一个用于返回的对象
    private static Cat instance;
    
    //私有化构造器,保证外部无法创建实例。
    private Cat(){
        System.out.println("对象创建了");
    }
    
    //创建实例
    public static Cat getInstance(){
        //如果instance为空,则新 new 一个对象
        if(instance==null){
            instance = new Cat();
        }
        //如果不为空,则直接返回
        return instance;
    }
   
}

  • 测试:
  //测试
    public static void main(String[] args){
    	 
        Cat c = Cat.getInstance();
        Cat c1 = Cat.getInstance();
        System.out.println(c == c1);//true
        
    }
    
2、线程安全问题
  • 懒汉模式是线程不安全的。当有并发出现时,会new出多个实例。可以通过加锁使之变成线程安全的。
3、给 getInstance( ) 方法加锁
  • 每次调用getInstance都会上锁,效率不高,适用于多线程环境
  • 示例:
//给方法加锁。
public static synchronized Cat getInstance(){
    //如果instance为空,则新 new 一个对象
    if(instance==null){
        instance = new Cat();
    }
    //如果不为空,则直接返回
    return instance;
}
4、双重检验锁(适用于多线程,效率相对更高,推荐使用)
  • 双重检验锁指在懒汉模式的基础上做进一步优化,给静态对象的定义加上volatile锁来保障初始化时对象的唯一性,在获取对象时通过synchronized (Cat.class)给单例类加锁来保障操作的唯一性。
  • 示例:
public class Cat {
    private String name;
    private String age;
    
    //volatile修饰成员变量 阻止指令重排序
    private static volatile Cat instance;

    //私有化构造器,保证外部无法创建实例。
    private Cat() {
        System.out.println("对象创建了");
    }

    //获取实例实例
    public static Cat getInstance() {
        if (instance == null) {
            //实例为空再加锁
            synchronized (Cat.class) {
                if (instance == null) {
                    instance = new Cat();
                }
            }
        }
        
        //如果不为空,则直接返回
        return instance;
    }
}

运行过程

  • 检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量;
  • 获取锁;
  • 第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量;
  • 否则,初始化并返回变量。

问题

  • 双重检测较好的解决了懒汉模式开销大的问题,但首次加载的时候效率依然较低。

拓展volatile关键字

三、单例模式之饿汉式

1、概念
  • 饿汉式就是在类加载的时候就创建实例。饿汉式本身就是线程安全的
  • 示例:
public class Cat {
    private String name;
    private String age;

    //类加载就创建实例,加上final关键字,防止重复创建
    private static final Cat instance = new Cat();

    //私有化构造器,保证外部无法创建实例。
    private Cat() {
        System.out.println("对象创建了");
    }
    
	//获取实例的方法
    public static Cat getInstance() {
        return instance;
    }
}

  • 测试:
  //测试
    public static void main(String[] args){
    	 
        Cat c = Cat.getInstance();
        Cat c1 = Cat.getInstance();
        System.out.println(c == c1);//true
        
    }
    

四、单例模式之静态内部类

1、概念:
  • 静态内部类通过在类中定义一个静态内部类,将对象实例的定义和初始化放在内部类中完成,我们在获取对象时要通过静态内部类调用其单例对象。之所以这样设计,是因为类的静态内部类在JVM中是唯一的,这很好地保障了单例对象的唯一性。

  • 示例:


public class Cat {
  
  	private static class CatHolder {
      	private static final Cat INSTANCE = new Cat();
    }
  
  	private Cat(){}
  
  	public static final Cat getInstance(){
      	return CatHolder.INSTANCE;
    }
}

五、对比总结

1、饿汉式:
  • 坏处:对象加载时间过长。
  • 好处:线程安全
2、懒汉式:
  • 好处:延迟对象的创建。
  • 坏处:线程不安全--->到多线程内容时,再修改
3、注意:
  • 如果一个对象使用频率不高,占用内存还特别大,明显就不合适用饿汉式了,这时就需要一种懒加载的思想,当程序需要这个实例的时候才去创建对象,就如同一个人懒的饿到不行了才去吃东西。