单例模式
单例模式属于创建型模式,⼀个单例类在任何情况下都只存在⼀个实例, 构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例,对外提供⼀ 个静态公有⽅法获取实例。
优点是内存中只有⼀个实例,减少了开销,尤其是频繁创建和销毁实例的 情况下并且可以避免对资源的多重占⽤。
缺点是没有抽象层,难以扩展, 与单⼀职责原则冲突。
单例模式的设计规则
由定义我们可以很清晰的抽象出: 实现Java单例模式类有哪些通用设计规则?
(1)私有化类构造器。
(2)定义静态私有的类对象。
(3)提供公共静态的获取该私有类对象的方法。
了解了单例模式的概念,以及单例模式的通用设计规则,对于如何实现一个Java单例,应该是没什么阻碍了。这里我们还是要思考下单例模式的优点,或者说有啥好处,使用场景是什么?带着这些问题我们就能更好的设计单例模式。
为什么使用单例
1.Java单例模式解决了什么问题?
答:Java的单例模式主要解决了多线程并发访问共享资源的线程安全问题。
2.Java单例模式主要应用场景有哪些?
答:1.共享资源的访问与操作场景,如Windows系统的资源管理器,Windows系统的回收站,显卡的驱动程序,系统的配置文件,工厂本身(类模板),应用程序的日志对象等。
2.控制资源访问与操作的场景,如数据库的连接池,Java的线程池等。
单例模式的常⻅写法有哪些?
1. 饿汉式,线程安全
public class Singleton {
private static final Singleton instance = new Singleton();
// 私有的默认构造函数
public Singleton() {
}
// 静态工厂方法
public static Singleton getInstance() {
return instance;
}
public void get() {
System.out.println("get");
}
public static void main(String[] args) {
Singleton.getInstance().get();
}
}
public class Singleton {
private static final Singleton instance = new Singleton();
// 私有的默认构造函数
private Singleton() {
}
// 静态工厂方法
public static Singleton getInstance() {
return instance;
}
public void get() {
System.out.println("get");
}
// public static void main(String[] args) {
// Singleton.getInstance().get();
// }
public static void main(String[] args) throws Exception {
// 使⽤反射破坏单例
// 获取空参构造⽅法
Constructor<Singleton> declaredConstructor =
Singleton.class.getDeclaredConstructor(null);
// 设置强制访问
declaredConstructor.setAccessible(true);
// 创建实例
Singleton singleton = declaredConstructor.newInstance();
System.out.println("反射创建的实例" + singleton);
System.out.println("正常创建的实例" +
Singleton.getInstance());
System.out.println("正常创建的实例" +
Singleton.getInstance());
}
}
结果:
2. 懒汉式,线程不安全
/**
* 懒汉式单例,线程不安全
*
*/
public class Singleton {
// 1、私有化构造⽅法
private Singleton(){ }
// 2、定义⼀个静态变量指向⾃⼰类型
private static Singleton instance;
// 3、对外提供⼀个公共的⽅法获取实例
public static Singleton getInstance() {
// 判断为 null 的时候再创建对象
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("多线程创建的单例:" +
Singleton.getInstance());
}).start();
}
}
}
结果:
懒汉式,线程安全
public class Singleton {
// 1、私有化构造⽅法
private Singleton(){ }
// 2、定义⼀个静态变量指向⾃⼰类型
private static Singleton instance;
// 3、对外提供⼀个公共的⽅法获取实例
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
// 1、私有化构造⽅法
private Singleton() {
}
// 2、定义⼀个静态变量指向⾃⼰类型
private volatile static Singleton instance;
// 3、对外提供⼀个公共的⽅法获取实例
public static Singleton getInstance() {
// 第⼀重检查是否为 null
if (instance == null) {
// 使⽤ synchronized 加锁
synchronized (Singleton.class) {
// 第⼆重检查是否为 null
if (instance == null) {
// new 关键字创建对象不是原⼦操作
instance = new Singleton();
}
}
}
return instance;
}
}
这⾥为什么要使⽤ volatile ?
public class Singleton {
// 1、私有化构造⽅法
private Singleton() {
}
// 2、对外提供获取实例的公共⽅法
public static Singleton getInstance() {
return InnerClass.INSTANCE;
}
// 定义静态内部类
private static class InnerClass{
private final static Singleton INSTANCE = new
Singleton();
}
}
枚举单例
public enum Singleton {
INSTANCE;
public void doSomething(String str) {
System.out.println(str);
}
}
Singleton singleton = Singleton.INSTANCE;
javap Singleton.class
Compiled from "Singleton.java"
public final class com.whm.demo.singleton.Singleton
extends
java.lang.Enum<com.whm.demo.singleton.Singleton> {
public static final
com.spring.demo.singleton.Singleton INSTANCE;
public static com.whm.demo.singleton.Singleton[]
values();
public static com.whm.demo.singleton.Singleton
valueOf(java.lang.String);
public void doSomething(java.lang.String);
static {};
通过反射验证破坏枚举,实现代码如下:
public class Test {
public static void main(String[] args) throws
Exception {
Singleton singleton = Singleton.INSTANCE;
singleton.doSomething("hello enum");
// 尝试使⽤反射破坏单例
// 枚举类没有空参构造⽅法,反编译后可以看到枚举有⼀个两个
参数的构造⽅法
Constructor<Singleton> declaredConstructor =
Singleton.class.getDeclaredConstructor(String.class,
int.class);
// 设置强制访问
declaredConstructor.setAccessible(true);
// 创建实例,这⾥会报错,因为⽆法通过反射创建枚举的实例
Singleton enumSingleton =
declaredConstructor.newInstance();
System.out.println(enumSingleton);
}
}
查看反射创建实例的 newInstance() ⽅法,有如下判断:
所以⽆法通过反射创建枚举的实例。
单例模式总结:
Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。
Singleton 模式一般不要实现 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。
如何实现多线程环境下安全的 Singleton? 需注意对双检查锁的正确实现。