1. 概念
1. 定义
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。它分为懒汉式和饿汉式。注意:
构造器必须私有化
对外必须获得一个公有的访问方式来获得实例。
2 优缺点
优点:
- 在内存中只有一个实例,减少了内存开销
- 可以避免对资源的多重占用,不会出现对同一个文件同时进行写操作
- 设置全局访问点,严格控制访问
缺点:
2. 代码实现
2.1 懒汉式
当程序第一次访问单例模式实例时才进行创建(延迟加载)。
1 单线程实现
缺点:只能在单线程下使用,多线程下会产生多个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class LazySingleton { private static LazySingleton singleton;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (singleton == null) { singleton = new LazySingleton(); }
return singleton; } }
|
2 加锁多线程
缺点:加锁进行同步,虽然可以保证单例,但效率太低,浪费大量时间。每一个线程进来调用getInstance(),都需要去获取锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Singleton2 { private static Singleton2 singleton;
private Singleton2() {
}
public static synchronized Singleton2 getInstance() { if (singleton == null) { singleton = new Singleton2(); } return singleton; }
}
|
3 双重检查机制
优点:保证单例的同时,也提高了效率。(每一个线程进来调用getInstance(),只有对象不为空,才需要去获取锁)
缺点:会被序列化和反射破坏单例(序列化可处理,反射不可处理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null; private LazyDoubleCheckSingleton(){
} public static LazyDoubleCheckSingleton getInstance(){ if(lazyDoubleCheckSingleton == null){ synchronized (LazyDoubleCheckSingleton.class){ if(lazyDoubleCheckSingleton == null){ lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton(); } } } return lazyDoubleCheckSingleton; } }
|
注意:singleton前面要加volatile关键字来保证程序运行的有序性,否则多线程访问下可能会出现对象未初始化错误!
说明:在内存中,创建一个变量需要三步:1.申请一块内存 ;2.调用构造方法初始化 ;3 分配一个指针指向这块内存
在编译原理中,有一个重要的内容叫做编译器优化,即在不改变原来语义的情况下,调整语句的执行顺序,来让程序运行的更快。因此存在这样一种情况,有两个线程A、B同时访问getInstance方法
A线程判断对象为空,没来得及进行第二次判断,(时间片用完了,B线程进入)
B线程判断对象为空,执行创建变量的3步,先申请一块内存,后分配一个指针指向这块内存,但还没有进行初始化(时间片用完了,A线程进入)
A线程接着执行,发现此时singleton已经不为空了,所以直接返回,但此时返回的singleton对象虽然B线程已经new了,但还没有初始化这个实例并没有构造完成,此时如果A线程使用这个实例,程序就会出现对象未初始化错误了。
2.2 饿汉式
顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。
优点:写法简单,在类加载的时候就完成实例化,避免线程同步。
1 简单写法
缺点:没有达到懒加载的效果,若果自始至终都没有用过这个对象,就会造成内存浪费。
1 2 3 4 5 6 7 8 9
| public class HungrySingleton{
private final static HungrySingleton hungrySingleton= new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance(){ return hungrySingleton; } }
|
2 静态内部类
这里采用了静态内部类实例singleton对象,静态内部类相当于一个静态属性,只有在第一次加载类时才会初始化,在类初始化时,别的线程是无法进入的,因此保证了线程安全。
缺点:会被序列化和反射破坏单例(序列化可处理,反射不可处理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class StaticInnerClassSingleton { private static class InnerClass{ private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance(){ return InnerClass.staticInnerClassSingleton; } private StaticInnerClassSingleton(){ if(InnerClass.staticInnerClassSingleton != null){ throw new RuntimeException("单例构造器禁止反射调用"); } } private Object readResolve(){ return InnerClass.staticInnerClassSingleton; } }
|
3 枚举单例(推荐)
对于这种方式的单例,不受反射和序列化的攻击,是最推荐的一种写法。
1
| 单元素的枚举类型已经成为实现Singleton的最佳方法 -- 出自 《effective java》
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public enum EnumInstance { INSTANCE; private Object data;
public Object getData() { return data; }
public void setData(Object data) { this.data = data; } public static EnumInstance getInstance(){ return INSTANCE; } }
|
测试序列化和反序列化。可以看出枚举不受序列化影响
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TestEnum { public static void main(String[] args) throws Exception { EnumInstance instance = EnumInstance.getInstance(); instance.setData(new Object());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); oos.writeObject(instance); File file = new File("singleton_file"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); EnumInstance newInstance = (EnumInstance) ois.readObject();
System.out.println(instance.getData()); System.out.println(newInstance.getData()); System.out.println(instance.getData() == newInstance.getData()); } }
|

测试反射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class TestEnumReflect { public static void main(String[] args) throws Exception { EnumInstance instance = EnumInstance.getInstance(); instance.setData(new Object());
Constructor<EnumInstance> constructor = EnumInstance.class.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); EnumInstance newInstance = constructor.newInstance("test", 111);
System.out.println(instance.getData()); System.out.println(newInstance.getData()); System.out.println(instance.getData() == newInstance.getData()); } }
|

可以看到,报错 Cannot reflectively create enum objects,无法通过反射创建枚举对象
3. 序列化和反射破坏单例
3.1 序列化破坏单例
1 2 3 4 5 6 7 8
| public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton= new HungrySingleton();
public static HungrySingleton getInstance(){ return hungrySingleton; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Test1 { public static void main(String[] args) throws IOException, ClassNotFoundException { HungrySingleton instance = HungrySingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); oos.writeObject(instance);
File file = new File("singleton_file"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance); System.out.println(newInstance); System.out.println(instance == newInstance); } }
|

解决方式:
HungrySingleton 增加readResolve()方法
1 2 3
| private Object readResolve(){ return hungrySingleton; }
|
3.2 反射破坏单例
只可避免反射破坏饿汉式单例,无法避免反射破坏懒汉式单例。
1. 饿汉式单例
对于饿汉式,即在类初始化时创建对象。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class TestReflect { public static void main(String[] args) throws Exception { HungrySingleton instance = HungrySingleton.getInstance();
Constructor<HungrySingleton> constructor = HungrySingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); HungrySingleton newInstance = constructor.newInstance();
System.out.println(instance); System.out.println(newInstance); System.out.println(instance == newInstance); } }
|

由上可看出,通过反射创建了两个不同的实例。可做如下调整:
当使用无参构造器构建对象时,由于对象已经在类加载时创建,则不允许再创建新对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
static{ hungrySingleton = new HungrySingleton(); } private HungrySingleton(){ if(hungrySingleton != null){ throw new RuntimeException("单例构造器禁止反射调用"); } } public static HungrySingleton getInstance(){ return hungrySingleton; } }
|
2. 懒汉式单例
即使在私有构造器中,增加了禁止反射调用的逻辑,也无法确保单例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class LazySingleton { private static LazySingleton lazySingleton = null; private LazySingleton(){ if(lazySingleton != null){ throw new RuntimeException("单例构造器禁止反射调用"); } } public synchronized static LazySingleton getInstance(){ if(lazySingleton == null){ lazySingleton = new LazySingleton(); } return lazySingleton; } }
|
- 当先通过反射创建对象,再通过方法调用创建对象的场景,就会产生两个不同的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class TestReflect2 { public static void main(String[] args) throws Exception { Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); LazySingleton newInstance = constructor.newInstance();
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance); System.out.println(newInstance); System.out.println(instance == newInstance); } }
|

当先通过方法调用创建对象的场景,再通过反射创建对象,则会根据逻辑阻止第二个对象的产生。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class TestReflect2 { public static void main(String[] args) throws Exception { LazySingleton instance = LazySingleton.getInstance();
Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); LazySingleton newInstance = constructor.newInstance(); System.out.println(instance); System.out.println(newInstance); System.out.println(instance == newInstance); } }
|

因为二者虽然都会调用无参构造器,但是只有getInstance()方法才会将lazySingleton赋值。