一、单例模式
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
通常我们可以通过调用类的静态变量来使一个对象被访问,但它不能防止你实例化多个该对象。一个最好的办法就是让类自身负责保存它的唯一实例,这样就可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。
二、实现单例模式的三个必要条件
- 构造函数私有化;
- 用于保存实例化后的对象的静态变量私有化;
- 获取实例的方法共有化
三、单例模式的角色
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的 getInstance() 工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个 Singleton 类型的静态对象,作为外部共享的唯一实例。
四、单例模式实现方式
单例模式除了能保证唯一的实例外,还可以控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。
通常有五种方式来实现单例模式,懒汉式、饿汉式、双重校验加锁式、静态内部类式、枚举类式。
4.1 懒汉式
需要使用时才实例化对象,节约内存。但多线程情况下可能会有多个线程同时判断singleton == null而实例化多个LazySingleton对象。
public class LazySingleton {
private static LazySingleton singleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
4.2 饿汉式
在类加载期间直接实例化一个单例,就算不使用它,也会占用内存。但线程安全。
public class HungrySingleton {
private static HungrySingleton singleton = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return singleton;
}
}
4.3 双重校验加锁式
懒汉式的线程安全形式,但获取锁和释放锁需耗费一定的CPU处理时间。
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton singleton = null;//要加volatile,保证线程间的可见性
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (singleton == null) {
//不让线程每次访问getInstance方法都加锁,只有在没有实例化时才加锁
synchronized (DoubleCheckSingleton.class) {
//锁的是类对象,不是实例对象singleton,因为singleton不一定被创建出来了
if (singleton == null) {
//如果有多个线程同时通过了第一层校验,在获得锁后如果不再校验一次,有可能实例已经被创建过了
singleton = new DoubleCheckSingleton();
}
}
}
return singleton;
}
}
4.4 静态内部类式
静态内部类只有在被调用时才会被加载,加载时会初始化这个内部类的静态变量,由虚拟机保证只会被初始化一次,这样就同时实现了延迟加载和线程安全。
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}
private static class Singleton {
public static StaticInnerClassSingleton singleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return Singleton.singleton;
}
}
4.5 枚举类式
枚举类实质上是功能齐全的类,因此可有自己的属性和方法,枚举是通过公有的静态final域为每个枚举常量导出实例的类。简洁高效安全,由虚拟机保障不会被实例化多次。
public enum EnumSingleton {
enumSingleton;
}
五、单例模式的安全性
单例模式的目标是,任何时候该类都只有唯一的一个对象。但是上面我们写的大部分单例模式都存在漏洞,被攻击时会产生多个对象,破坏了单例模式。
5.1 序列化攻击
通过Java的序列化机制来攻击饿汉式单例模式:
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton singleton = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(singleton); // 序列化
ObjectInputStream ois = new ObjectInputStream(