单例模式
当我们的程序中只需要某个类存在一个对象时,比如web应用中的ServletContext 的对象、Spring中的AppliationContext 的对象,都只需要一个,从程序运行开始,到程序结束。
所以我们需要使用单例模式来创建这些对象。
单例模式之饿汉式
饿汉式:顾名思义,饿汉式会迫不及待地创建对象,所以在类加载完成后就会创建对象。
class Singleton{
// 单例模式不允许使用new 方式创建对象,所以需要将构造器私有化
private Singleton(){
}
private static final Singleton SINGLETON=new Singleton();
public static Singleton getInstance(){
return SINGLETON;
}
}
还可以使用静态块进行初始化,使用静态块初始化是在类初始化阶段给SINGLETON赋值。使用上面这种常量方式会在类的链接中的准备阶段显示初始化,而类加载是一个线程安全的操作,不会创建两个对象。
但是饿汉式的单例模式有一个很大的缺点,如果这个类存在占用很大内存的成员变量,对象创建后就会占用极大的内存,但是仅仅是加载了这个类,而不需要这个类的对象,就会造成空间的浪费。
所以我们需要懒汉式。
单例模式之懒汉式
懒汉式:顾名思义,很懒,所以是在我们需要的时候才会去创建对象,就不会造成空间浪费的情况。
/**
* 这种方式线程不安全
*/
class Singleton {
private Singleton() {
}
private static Singleton singleton;
public static Singleton getInstance() {
if (singleton == null) {
// 如果一条线程运行到了这里,cpu切换了线程,会造成多条线程获取的对象不一样
singleton = new Singleton();
}
return singleton;
}
}
/**
* 加了同步锁的方法,效率低
*/
class Singleton {
private Singleton() {
}
private static Singleton singleton;
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
/**
* 使用同步代码块方式,双重检查,效率可以
*/
class Singleton{
private Singleton(){
}
private static volatile Singleton singleton;
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
在上面我们使用了==volatile==关键字修饰单例对象。
创建对象的步骤:
- 加载类元信息
- 为对象分配内存
- 处理并发问题
- 属性的默认初始化(零值初始化)
- 设置对象头信息
- 属性的显示初始化、代码块中初始化、构造器中初始化
- 将引用指向这个对象
但是由于指令重排的问题,可能对象还未初始化的时候,引用就指向了对象,所以需要使用volatile来防止指令重排。
因为类加载是线程安全的,所以可以使用内部类的方式创建单例对象。
class Singleton{
private Singleton(){
}
private static class InnerClass{
private static final Singleton SINGLETON=new Singleton();
}
public static Singleton getInstance(){
return InnerClass.SINGLETON;
}
}
但是无论使用饿汉式还是懒汉式都无法阻挡==反射==创建对象。反射可以无视构造器的访问权限,所以最安全的单例模式只有枚举类了。
单例模式之枚举类
enum Singleton{
SINGLETON
}
枚举的值就是枚举类的一个对象,为什么枚举类可以防止反射呢?
java.lang.reflect.Constructor 类有
if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");
会抛出异常。
使用Class对象的 newInstance() 方法创建枚举类对象会抛出 java.lang.InstantiationException。
并且它在 jdk9的时候被标记为 @Deprecated 。