单例模式介绍:
保证一个类只有一个实例,并提供一个全局访问点,
单例模式为了防止外部主动创建内,故而把构造方法设为私有
单例模式构建思路:
对象是怎么来的?new=>调用构造方法。所以需要控制构造方法,不允许随便在外部创建对象
1、私有化构造方法
那外部怎么得到对象呢?通过自己提供的get方法return一个对象。同时get方法里不能创建对象,不然外部每次调用get方法都会创建对象,所以对象只能在get方法外面创建。get方法不能通过对象调用了,所以必须声明为static方法,通过类名.get方法调用。
2、创建一个静态的get方法
创建一个成员变量保存对象,由于不能让外部访问,所以应该用private修饰。又因为需要get方法访问,而get方法是static修饰的,只能访问static修饰的成员变量,所以该变量也要用static修饰。还可以加上一个final表示不可变性。
3、在类中声明一个private、static、final修饰的成员变量用于保存对象。
饿汉模式:
public class Singleton {
/**
* 3. 创建对象
*/
private static final Singleton INSTANCE = new Singleton();
/**
* 1. 私有化构造方法
*/
private Singleton(){}
/**
* 饿汉模式
* 2. 在类的内部创建一个对象,并且将该对象,返回给外部,通过get方法
* 但是非static方法,只能通过对象调用,而我们不能在外部得到一个对象,所以只能通过类.get方法的方式调用,所以该方法要声明为 static方法
* @return
*/
public static Singleton getInstance(){
return INSTANCE;
}
}
懒汉模式:
public class Singleton {
private static Singleton INSTANCE;
/**
* 1. 私有化构造方法
*/
private Singleton(){}
/**
* 懒汉模式
* 2. 在类的内部创建一个对象,并且将该对象,返回给外部,通过get方法
* 但是非static方法,只能通过对象调用,而我们不能在外部得到一个对象,所以只能通过类.get方法的方式调用,所以该方法要声明为 static方法
* @return
*/
public static Singleton getInstance(){
//判断当前对象是否已经创建了
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
饿汉模式:天生就是线程安全的
优势:
因为在类一加载就创建了对象,所有当需要这个对象的时候,获取对象的效率高
劣势:
类加载的时候,效率低,会一直占用内存空间,只有类卸载,才能释放空间
懒汉模式: 当需要对象的时候才创建对象。
优势:
节省资源空间,线程不安全
劣势:
获取对象的时候,临时创建,所以效率低
饿汉的优化:(用静态代码块创建对象,比较:没优化前是在编译的时候(类加载的时候)就会创建对象,静态代码块是在该类被使用的时候才会被调用,变得和懒汉一样,在需要创建对象的时候才会被调用)
/**
* 饿汉模式:
* 优点:
* 线程安全
* 获取对象效率高
*
* 缺点:占用空间,类加载效率低
*
* 优化的是启动速度
* 静态代码块在需要的时候加载内部的代码
*/
public class Singleton {
private static final Singleton INSTANCE;
//优化代码
static{
System.out.println("静态代码块");
INSTANCE = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
懒汉的优化:(双重校验锁)
/**
* 懒汉模式:
* 优点:
* 节省空间,启动速度快
*
* 缺点:
* 线程不安全
* 获取对象效率低
*
* 优化:双重校验锁,优化的时线程安全问题
*/
public class Singleton {
private volatile static Singleton INSTANCE;
private Singleton(){
}
public static Singleton getInstance() {
//判断是否已经被创建对象,如果创建了,则直接返回创建好的对象
if (INSTANCE == null) {
//有线程安全的代码,同步
synchronized (Singleton.class) {
//避免线程安全问题,再次判断是否被创建了对象
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
分析:
更高效的懒汉模式:
public class Singleton {
/**
* 类级的内部类,也就是静态的成员式内部类,和其他被static修饰的成员类似
* 属于类级别的,和对象无关,只有在被调用的时候才会被装载,由于类被装载
* 时属于线程安全的,即由JVM内部为我们保证线程安全
*/
private static class SingletonHolder{
/**
* 静态初始化器,有JVM来保证线程安全
*/
private static Singleton instance = new Singleton();
}
/**
* 私有化构造方法
*/
private Singleton(){
if (SingletonHolder.instance != null) {
throw new IllegalStateException();
}
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
相比于懒汉以及饿汉模式,静态内部类模式(一般也被称为 Holder)是许多人推荐的一种单例的实现方式,因为相比懒汉模式,它用更少的代码量达到了延迟加载的目的。
顾名思义,这种模式使用了一个私有的静态内部类,来存储外部类的单例,这种静态内部类,一般称为 Holder。
而利用静态内部类的特性,外部类的 getinstance() 方法,可以直接指向 Holder 持有的对象。
- 反射能否打破单例?
首先,对外部类的私有构造器中加入 instance==null 的判断,防止反射入侵外部类。
其次,静态内部类保证了从外部很难获取 SingletonHolder 的 Class 对象,从而保证了内部类不会被反射。
- 多线程能否打破单例?
Holder 模式借用了饿汉模式的优势,就是在加载类(内部类)的同时对 instance 对象进行初始化。
由于自始至终类只会加载一次,所以即使在多线程的情况下,也能够保持单例的性质。
- 优势?劣势?
优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
劣势:需要多加载一个类;相比于懒汉模式,Holder 创建的单例,只能通过 JVM 去控制器生命周期,不能手动 destroy。