文章目录
- 1、懒汉式
- 2、双重检查
- 3、静态内部类
- 4、饿汉式
- 5、枚举
- 6、单例模式的破坏:序列化和反序列化
- 7、单例模式的破坏:反射
- 单例模式即在程序中想要保持一个实例对象,让某个类只有一个实例
- 单例类必须自己创建自己唯一的实例,并对外提供
- 优点:减少了内存开销
单例模式的实现,有以下几种思路:
1、懒汉式
在需要使用对象的时候,才会创建。
public class LazySingleton {
private static LazySingleton lazySingleton = null;
/**
* 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
*/
private LazySingleton() {
}
/**
* 单例对象的获取
*/
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
测试类启两个线程获取对象:
public class Test {
public static void main(String[] args) {
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName() + "-->" + instance);
}, "t1").start();
new Thread(() -> {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName()+ "-->" + instance);
}, "t2").start();
}
}
发现可能出现获取到两个不同对象的情况,这是因为线程安全问题:
两个线程A、B,同时执行完
IF 这一行,被挂起,再被唤醒时继续往下执行,就会创建出两个不同的实例对象。那最先想到的应该是synchronized关键字解决,但这样性能底下,因为不管对象是否为null,每次都要等着获取锁。
//性能低下,一刀切,不可行
public static synchronized LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
2、双重检查
通过两个IF判断,加上同步锁进行实现。
public class DoubleCheckSingleton {
private static DoubleCheckSingleton doubleCheckSingleton;
/**
* 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
*/
private DoubleCheckSingleton(){
}
public static DoubleCheckSingleton getInstance(){
if(doubleCheckSingleton == null){
synchronized (DoubleCheckSingleton.class){
if(doubleCheckSingleton == null){
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
}
如此,再有A、B两个线程同时执行到第一个IF,只能有一个成功创建对象,另一个获取到锁后,第二重判断会告诉它已经有对象实例了。而亮点则在于,后面来获取对象的线程不用等着拿锁,第一个IF就能告诉它已有对象,不用再等锁了。
3、静态内部类
在单例类中,通过私有的静态内部类,创建单例对象。(加private修饰词的,出了本类无法调用和访问)
public class StaticInnerClassSingleton {
/**
* 私有的静态内部类实例化对象
* 给内部类的属性赋值一个对象
*/
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
/**
* 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
*/
private StaticInnerClassSingleton(){
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
}
外部类StaticInnerClassSingleton被加载时,其对象不一定被初始化,因为内部类没有被主动使用到。直到调用getInstance方法时,静态内部类InnerClass被加载,完成实例化。
静态内部类在被加载时,不会立即实例化,而是在第一次使用时才会被加载并初始化。
这种延迟加载的特性,使得我们可以通过静态内部类来实现在需要时创建单例对象。
4、饿汉式
- 在调用时,就会创建单例对象
- 通过静态代码块或者静态变量直接初始化
public class HungrySingleton {
// 方式一:静态变量直接初始化
// private static final HungrySingleton hungrySingleton = new HungrySingleton();
private static HungrySingleton hungrySingleton = null;
// 方式二:静态代码块
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
5、枚举
- 单例模式的最佳实现方式
- 可有效防止对单例模式的破坏
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
6、单例模式的破坏:序列化和反序列化
通过流将单例对象,序列化到文件中,然后再反序列化读取出来。发现通过反序列化方式创建出来的对象内存地址,和原对象不一样,单例模式被破坏。
public class TestSerializer {
public static void main(String[] args) throws Exception {
//懒汉式
LazySingleton instance = LazySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton"));
LazySingleton objInstance = (LazySingleton) ois.readObject();
System.out.println(instance);
System.out.println(objInstance);
System.out.println(instance == objInstance);
}
}
可以发现单例模式的五种实现方式中,只有枚举不会被破坏单例模式。如果非要用其他几种模式,可以加readResolve方法来重写反序列化逻辑。因为反序列化创建对象时,是通过反射创建的,反射会调用readResolve方法。没有重写readResolve方法时,会通过反射创建一个新的对象,从而破坏了单例模式。
public class LazySingleton implements Serializable {
private static LazySingleton lazySingleton = null;
/**
* 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
*/
private LazySingleton() {
}
/**
* 单例对象的获取
*/
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
private Object readResolve(){
return lazySingleton;
}
}
也可考虑使用@JsonCreator注解。
7、单例模式的破坏:反射
- 通过字节码对象,创建构造器对象
- 通过构造器对象,初始化单例对象
- 由于单例对象的构造方法是private私有的,调用构造器中的方法,赋予权限,创建单例对象
注意私有修饰词时,反射会IllegalAccessException
处理下private问题,用懒汉模式验证:
public class TestReflect {
public static void main(String[] args) throws Exception{
//创建字节码对象
Class<LazySingleton> clazz = LazySingleton.class;
//构造器对象
Constructor<LazySingleton> constructor = clazz.getDeclaredConstructor();
//赋予权限
constructor.setAccessible(true);
//解决了私有化问题,获取实例对象
LazySingleton instanceReflect = constructor.newInstance();
//直接获取单例对象
LazySingleton instanceSingle = LazySingleton.getInstance();
System.out.println(instanceReflect);
System.out.println(instanceSingle);
System.out.println(instanceReflect == instanceSingle);
}
}
用枚举的方式验证:
public class TestEnumReflect {
public static void main(String[] args) throws Exception {
Class<EnumSingleton> clazz = EnumSingleton.class;
//枚举下的单例模式,创建构造方法时,需要给两个参数,薮泽NoSuchMethodException
//这两个参数是源码中的体现,一个是String,一个是int
Constructor<EnumSingleton> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingleton instanceReflect = constructor.newInstance("test",1234);
EnumSingleton instanceSingleton = EnumSingleton.getInstance();
System.out.println(instanceReflect);
System.out.println(instanceSingleton);
System.out.println(instanceReflect == instanceSingleton);
}
}
运行报错:Cannot reflectively create enum objects
,即反射创建枚举的单例对象,是不允许的:
在其他单例模式的实现方式里,也可以实现不允许通过反射创建对象。