单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,单例模式是创建型模式。
单例模式分为饿汉式单例和懒汉式单例
饿汉式单例:
public class Hungry {
private final static Hungry hungry = new Hungry();
private Hungry() { // 构造器私有
}
public static Hungry getInstance(){
return hungry;
}
}
- 饿汉式单例在类被初始化时就已经在内存中创建了对象,可能造成空间资源的浪费
- 以空间换时间,不存在线程安全问题
懒汉式单例:
public class Lazy {
private static Lazy single;
private Lazy() { // 构造器私有
}
public static Lazy getInstance(){
if (single==null){
single = new Lazy();
}
return single;
}
}
- 懒汉式单例在方法被调用后才创建对象,以时间换空间,在多线程环境下存在风险
public class Lazy {
private static Lazy single;
private Lazy() { // 构造器私有
System.out.println(Thread.currentThread().getName()+" ok");
}
public static Lazy getInstance(){
if (single==null){
single = new Lazy();
}
return single;
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
}
测试:
此时还是创建了5个对象,说明懒汉式单例在多线程下不安全
双重检测(Double Check Lock)懒汉式单例——DCL懒汉式单例
public class Lazy {
private static Lazy single;
private Lazy() { // 构造器私有
}
// 双重检测锁模式的懒汉式单例——DCL懒汉式
public static Lazy getInstance(){
if (single==null){
synchronized (Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
}
- 双重检测可以保证多线程安全
- 第一次判断 single==null 是为了避免非必要加锁,提高性能,因为锁的创建和释放会消耗很多资源
真正的DCL懒汉式单例
public class Lazy {
private static volatile Lazy single; // 加上volatile
private Lazy() { // 构造器私有
}
public static Lazy getInstance(){
if (single==null){
synchronized(Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
}
getInstance方法中 single = new Lazy() 不是一个原子性操作,创建对象的过程是:
① 在堆内存中开辟内存空间
② 执行构造方法,初始化对象
③ 把这个对象指向内存空间
极端情况,JVM中可能发生指令重排,执行过程变为 1 3 2 ,所以必须在 single 前面加上 volatile,volatile 能保证线程间的可见性,防止指令重排
静态内部类单例
public class Holder {
private Holder(){
}
public static class InnerClass{
private static final Holder single = new Holder();
}
public static Holder getInstance(){
return InnerClass.single;
}
}
- 静态内部类形式的单例可保证线程安全,也能保证单例的唯一性
- 在需要使用的时候内部类才被初始化,防止了空间资源的浪费
反射暴力破坏单例
public class Lazy {
private static volatile Lazy single; // 加上volatile
private Lazy() { // 构造器私有
}
public static Lazy getInstance(){
if (single==null){
synchronized(Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
// 反射暴力破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy single1 = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
}
测试:
此时可以在构造方法中加锁解决,只修改构造方法为:
private Lazy() { // 构造器私有
synchronized (Lazy.class){
if (single!=null){
throw new RuntimeException("不要使用反射来破坏到单例");
}
}
}
测试:
此时程序抛出异常,保护单例模式不被破坏
但此时若又修改测试方法为:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single1 = declaredConstructor.newInstance();
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
测试:
单例又再次被破坏,但我们可以再增加一个标志位flag,然后修改构造方法,其他不变:
public class Lazy {
private static volatile Lazy single; // 加上volatile
private static boolean flag = false;
private Lazy() { // 构造器私有
synchronized (Lazy.class){
if (flag==false){
flag = true;
}else {
throw new RuntimeException("不要使用反射来破坏到单例");
}
}
}
public static Lazy getInstance(){
if (single==null){
synchronized(Lazy.class){
if (single==null){
single = new Lazy();
}
}
}
return single;
}
// 反射暴力破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single1 = declaredConstructor.newInstance();
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
}
测试:
测试结果又报异常,保护单例不被破坏
但我们也可以通过反射来得到flag并修改它的值,从而破坏单例,修改测试方法为:
public static void main(String[] args) throws Exception {
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
Lazy single1 = declaredConstructor.newInstance();
Field flag = Lazy.class.getDeclaredField("flag"); // 通过反射获得flag属性
flag.set(single1,false); // 把flag的值改为false
Lazy single2 = declaredConstructor.newInstance();
System.out.println(single1.hashCode());
System.out.println(single2.hashCode());
}
测试:
这说明在反射下,上面的这些单例模式都是不安全的!所以我们可以使用枚举实现单例!
枚举单例
public enum EnumSingle {
SINGLE;
public EnumSingle getInstance(){
return SINGLE;
}
}
- 枚举本身实现了单例
- 反射不能破坏枚举
反射的newInstance()方法写死了不能破坏枚举
参考:
https://blog.csdn.net/as513385/article/details/110082439.