带你认识单例模式之七种不同实现方式,最后一种堪称王炸,你绝对没见过!

单例模式之七种不同实现方式

一、饿汉式

顾名思义,这是一个饿汉子,上来就将食物塞进去(实例化),这种方式最简单粗暴

实现方式

java 复制代码
/**
 * 饿汉式单例模式
 */
public final class HungrySingleton {

    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
        System.out.println("HungrySingleton is created");
    }

    public static HungrySingleton getInstance() {
        return instance;
    }

}

测试代码

java 复制代码
public static void testHungrySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName());
                    HungrySingleton instance = HungrySingleton.getInstance();
                    System.out.println(instance);
                }
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
HungrySingleton is created

二、懒汉式

懒汉式,这个方法非常懒,就像我上学时一样,作业不写,偏偏等到要上学的前几天才开始动手疯狂补作业。

java 复制代码
/**
 * 懒汉式单例模式
 * 不要一开始就初始化实例,而是等到要用到的时候再去初始化实例
 */
public final class LazySingleton {

    private static LazySingleton instance = null;

    private LazySingleton() {
        System.out.println("LazySingleton constructor");
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

}

测试代码

java 复制代码
/**
* 懒汉式单例模式
*/
public static void testLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                LazySingleton instance = LazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor
LazySingleton constructor

总结

上面代码在多线程情况下出现了多次实例化的情况,假如线程1正在做if (instance == null)判断,此时CPU开始调度,线程2进入if (instance == null)并且创建了一个实例,然后放弃CPU使用权,当线程1获取到CPU使用权之后就会从停止的地方继续开始执行未完成的代码,此时就会再一次实例化,出现多次实例化的情况。因此这种情况在多线程情况下并不能保证单例

三、synchronized方式

为了解决懒汉式多线程情况下多次实例化的问题,简单粗暴的方式就是加锁,直接getInstance方法上锁,保证仅一个线程同时访问此方法。

java 复制代码
public class SynchronizedLazySingleton {

    private static SynchronizedLazySingleton instance = null;

    private SynchronizedLazySingleton() {
        System.out.println("Synchronized Lazy Singleton");
    }

    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }

}

测试代码

java 复制代码
public static void testSynchronizedLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                SynchronizedLazySingleton instance = SynchronizedLazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
Synchronized Lazy Singleton

思考

上面加锁的粒度是不是太大了,多线程情况下同时仅有一个线程才能访问getInstance方法了,有没有办法再优化一下呢,下面就是双重检查的实现方式了。

四、双重检查(double check)

在线程进入时判断一次,为空则加锁,进入之后再判断一次,至于为什么要再判断一次,这是为了防止两次实例化。假设线程1进入了第一个判断,此时释放了CPU;线程2进来之后,同样可以执行进入第一个判断并且加锁,然后实例化,最后释放CPU;然后等到线程1再次拿到CPU之后,从上一次未执行的地方开始,对DoubleCheckLazySingleton.class加锁,由于没有第二个判断,此时线程1就可以完成第二次实例化了;因此,这就是为什么要加两次判断的原因。

java 复制代码
/**
 * 双重检查懒汉式单例模式
 */
public class DoubleCheckLazySingleton {

    private static DoubleCheckLazySingleton instance = null;

    private DoubleCheckLazySingleton() {
        System.out.println("DoubleCheckLazySingleton is created");
    }

    public static DoubleCheckLazySingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckLazySingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

}

测试代码

java 复制代码
    public static void testDoubleCheckLazySingleton() {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    DoubleCheckLazySingleton instance = DoubleCheckLazySingleton.getInstance();
                }
            }, "Thread" + i).start();
        }
    }

运行结果

java 复制代码
DoubleCheckLazySingleton is created

五、双重检查+可见性(double check + volatile)

为什么要加volatile,因为我代码没有写完,如果此时单例类中还有其他的字段,就容易出现空指针异常,因为这些字段对于其它线程是不可见的,需要加上volatile保证对于其它线程也可见。

java 复制代码
/**
 * 双重检查懒汉式单例模式+volatile
 */
public class VolatileDoubleCheckLazySingleton {

    private volatile static VolatileDoubleCheckLazySingleton instance = null;

    private VolatileDoubleCheckLazySingleton() {
        System.out.println("VolatileDoubleCheckLazySingleton construct");
    }

    public static VolatileDoubleCheckLazySingleton getInstance() {
        if (instance == null) {
            synchronized (VolatileDoubleCheckLazySingleton.class) {
                if (instance == null) {
                    instance = new VolatileDoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }

}

测试代码

java 复制代码
/**
* 双重检查单例模式 + volatile
*/
public static void testVolatileDoubleCheckLazySingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                VolatileDoubleCheckLazySingleton instance = VolatileDoubleCheckLazySingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
VolatileDoubleCheckLazySingleton construct

六、holder内部类实例化

借助于内部类来实例化单例,秀不秀?

java 复制代码
public class HolderSingleton {
    private static HolderSingleton instance;

    private HolderSingleton() {
        System.out.println("HolderSingleton constructor");
    }

    private static class Holder {
        private static final HolderSingleton INSTANCE = new HolderSingleton();
    }

    public static HolderSingleton getInstance() {
        return Holder.INSTANCE;
    }

}

测试代码

java 复制代码
/**
* Holder
*/
public static void testHolderSingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                HolderSingleton instance = HolderSingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
HolderSingleton constructor

七、枚举类实现方式

王炸!枚举类天然的定义就是单例,这没的说。

java 复制代码
public enum EnumSingleton {

    INSTANCE,
    ;

    private EnumSingleton() {
        System.out.println("INSTANCE will be initialized immediately");
    }

    public static void method() {

    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }

}

测试代码

java 复制代码
/**
* EnumSingleton
*/
public static void testEnumSingleton() {
    for (int i = 0; i < 1000; i++) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                EnumSingleton instance = EnumSingleton.getInstance();
            }
        }, "Thread" + i).start();
    }
}

运行结果

java 复制代码
INSTANCE will be initialized immediately
相关推荐
idolyXyz16 分钟前
[java: Cleaner]-一文述之
java
一碗谦谦粉27 分钟前
Maven 依赖调解的两大原则
java·maven
emplace_back38 分钟前
C# 集合表达式和展开运算符 (..) 详解
开发语言·windows·c#
jz_ddk44 分钟前
[学习] C语言数学库函数背后的故事:`double erf(double x)`
c语言·开发语言·学习
萧曵 丶1 小时前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
netyeaxi1 小时前
Java:使用spring-boot + mybatis如何打印SQL日志?
java·spring·mybatis
xiaolang_8616_wjl1 小时前
c++文字游戏_闯关打怪2.0(开源)
开发语言·c++·开源
收破烂的小熊猫~1 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
猴哥源码1 小时前
基于Java+SpringBoot的动物领养平台
java·spring boot