单例模式是确保只有一个类的实例,这个唯一实例可以在全局访问。单例模式一般的作用范围是在一个classLoader范围内,也就是一个应用级别。
根据具体的业务需求,例如当前登录用户,我们在程序中任何地方访问的都是这个用户。或者一些资源消耗比较大的实例,我们不想频繁的去创建。
饿汉模式
// 饿汉模式
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:
- 线程安全,写法简单
缺点:
- 一上来就初始化,不是按需加载的;
- 无法干预单例的初始化;
- 任何静态成员的访问,都会触发单例的构造
如果所需的单例占用的资源很少,并且也不依赖于其他数据,并且在项目中一定会使用到的实例,可以使用这种方式。
懒汉模式
模式1:非线程安全的
最基本的懒汉模式
// 懒汉模式,不是线程安全的
public class Singleton {
private static Singleton instance;
/**
* 私有化构造函数防止外界实例化
*/
private Singleton() {
/**防止使用反射来实例化*/
if (instance != null) {
throw new IllegalStateException("实例已经存在");
}
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
缺点:
- 非线程安全的
模式2:简单粗暴的线程安全
public class Singleton {
private static Singleton instance;
/**
* 私有化构造函数防止外界实例化
*/
private Singleton() {
/**防止使用反射来实例化*/
if (instance != null) {
throw new IllegalStateException("实例已经存在");
}
}
// 和模式1相比对了关键字:synchronized
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
缺点:
- 保证了线程安全,我们想要的只是首次实例化时锁定,这种方式每次都会锁定,效率不高。
模式3:双重检验线程安全
public class Singleton {
/**
* 添加了volatile关键字,是为了限制处理器进行指令优化重排,防止出现异常的情况
*/
private static volatile Singleton instance;
/**
* 私有化构造函数防止外界实例化
*/
private Singleton() {
/**防止使用反射来实例化*/
if (instance != null) {
throw new IllegalStateException("实例已经存在");
}
}
/**
* 双重判断,保证线程安全
*/
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点:
- 是线程安全的
静态内部类
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return HelperHolder.INSTANCE;
}
private static class HelperHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
优点:
- 线程安全的,兼顾了饿汉模式和懒汉模式的优点
-
写法简单
破坏单例手段:反射和序列化
1、反射的方式,可以在构造函数中写判断。
**
* 私有化构造函数防止外界实例化
*/
private DoubleCheckingThreadSafe() {
/**防止使用反射来实例化*/
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}
2、反序列化,使用readResolve方法
// 不添加该方法则会出现 反序列化时出现多个实例的问题
public Object readResolve() {
return instence;
}
终极武器
使用枚举,枚举本质也是一个类。简单、高效、安全。这种写法在功能上与共有域方法相近,但是它更简洁,无偿地提供了序列化机制,绝对防止对此实例化,即使是在面对复杂的序列化或者反射攻击的时候。虽然这中方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法
public enum SingleInstance {
INSTANCE;
public void fun1() {
// do something
}
}
// 使用SingleInstance.INSTANCE.fun1();