目录
单例模式的三种实现方式,分别是:饿汉式、懒汉式和静态内部类实现。
饿汉式
即在一开始就将单例对象初始化,这样后面用户调用获取对象的方法时直接返回创建好的对象引用就行。
优点是当用户第一次获取单例对象时可以节省时间,缺点是若单例对象创建后迟迟不被获取就会占用内存空间。
/** 饿汉式
* @author zkw
* @date 2020/7/5 8:30
*/
public class Singleton1 {
// 不注释的话可以通过反射获取和修改obj属性
private Singleton1(){}
public static void main(String[] args) {
System.out.println(Singleton1.getInstance());
System.out.println(Singleton1.getInstance());
}
private static final Singleton1 obj = new Singleton1();
public static Singleton1 getInstance(){
return obj;
}
}
懒汉式
当用户调用获取对象的方法时检查单例对象是否已经初始化,否就初始化,是就直接返回对象引用。注意懒汉式的getInstance()必须要进行同步,否则可能出现线程安全问题。
优缺点与饿汉式相反,同时可能发生线程安全问题。
/** 懒汉式
* @author zkw
* @date 2020/7/5 8:50
*/
public class Singleton2 {
// 不注释的话可以通过反射获取和修改obj属性
private Singleton2(){}
private static Singleton2 obj = null;
public static void main(String[] args) {
System.out.println(Singleton2.getInstance());
System.out.println(Singleton2.getInstance());
}
public static Singleton2 getInstance(){
if (obj == null){
synchronized (Singleton2.class){
if (obj == null){
obj = new Singleton2();
}
}
}
return obj;
}
}
静态内部类
是懒汉式的优化版,也是用户第一次获取时进行初始化,不过开发者不用自己去控制线程安全问题,而是交给了JVM去管理。
/** 静态内部类
* @author zkw
* @date 2020/7/5 8:58
*/
public class Singleton3 {
private Singleton3(){}
public static void main(String[] args) {
System.out.println(Singleton3.getInstance());
System.out.println(Singleton3.getInstance());
}
public static Singleton3 getInstance(){
return InnerSingleton3.obj;
}
private static class InnerSingleton3{
// 不注释的话可以通过反射获取和修改obj属性
private InnerSingleton3(){}
private static final Singleton3 obj = new Singleton3();
}
}
私有构造方法的好处
可以防止通过反射来获取和修改单例对象。私有构造方法能防止在类外部通过new的方式来创建类对象,又因为在反射中通过Class对象的newInstance()方法也是调用类的空参构造器来创建对象的,所以也能防止反射机制对单例模式的破坏。不能创建Singleton对象,那么破坏就无从谈起。
/**
* Creates a new instance of the class represented by this {@code Class}
* object. The class is instantiated as if by a {@code new}
* expression with an empty argument list.
**/
@CallerSensitive
public T newInstance(){
......
}
通过反射机制可以获取对象中的私有属性(引用),下面obj被final修饰,不能让obj引用指向新的对象,但可以修改obj当前指向对象的内部属性。下面是实验代码,证明当构造方法共有时,可以通过反射获取单例对象引用:
public class Singleton1 {
// 公共构造方法
public Singleton1(){}
private static final Singleton1 obj = new Singleton1();
public static Singleton1 getInstance(){
return obj;
}
}
class Test{
public static void main(String[] args) throws Exception {
Singleton1 instance = new Singleton1();
Singleton1 instance1 = instance.getInstance(); // 通过接口获取单例对象
System.out.println(instance1); // 输出Singleton1@12a3a380
// 通过反射获取单例对象引用“obj”
Class<? extends Singleton1> cla = instance.getClass();
Field field = cla.getDeclaredField("obj");
field.setAccessible(true); // 将私有属性设置为可访问
Singleton1 obj = (Singleton1)field.get(instance);
System.out.println(obj); // 输出Singleton1@12a3a380
}
}