单例模式简介
单例单例,单个实例。简单来说就是在运行时,内存中有且只有一个实例存在。
如何实现单例?
首先既然只能由一个实例,也就是其他类或者方法中,不能调用new来获得一个实例,所以我们把构造器定义为私有!然后我们把这个实例用 getInstance()方法来获取这个实例就可以。
饿汉式
顾名思义,饿汉就是看到吃着就着急了,就开始干活挣饭钱了。所以在没有任何方法需要它时,它就准备好一切准备调用。
public class Singleton {
//饿汉式
private static final Singleton Instance = new Singleton();
//把构造器定义为私有
private Singleton() {
}
public static Singleton getInstance(){
return Instance;
}
//测试
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
懒汉式
同样,也是顾名思义,懒汉就是非常懒,只有在你需要他的时候才干活。
所以在getInstance()方法被调用时,才会创建这个实例对象。
但是普通的懒汉式在多线程环境下会产生错误。所以我们需要进行DCL(DoubleCheckLock)在锁的内部还要进行判断当前的实例是否存在。
并且这个实例必须用 volatile 修饰 ,因为创建一个对象是5条指令,我们必须避免这5条指令重排,因为有可能重排之后,并没有在内存空间上创建好对象,就返回了该引用。
class Singleton1{
//先不初始化
//volatile 避免指令重排,初始化是5条指令,避免还没有初始化完成就返回值。
private static volatile Singleton1 Instance = null;
private Singleton1(){}
public static Singleton1 getInstance() {
//双重检查保证多线程环境
if(Instance == null){
synchronized(Singleton1.class){
if(Instance == null){
Instance = new Singleton1();
}
}
}
return Instance;
}
//测试
public static void main(String[] args) {
//多线程测试
for(int i = 0; i < 10000;i++){
new Thread(()->{
System.out.println(Singleton1.getInstance().hashCode());
}).start();
}
}
}
静态内部类
静态内部类,通过名字我们可以猜想,内部类只有外部类才能够调用,所以我们把 Instance 定义到内部类中,能直接够避免多线程访问而产生的错误。
class Singleton2{
private Singleton2(){}
//静态内部类
private static class SingletonHolder{
private final static Singleton2 Instance = new Singleton2();
}
public Singleton2 getInstance(){
return SingletonHolder.Instance;
}
public static void main(String[] args) {
for(int i = 0 ; i < 10000 ;i++){
Singleton2 singleton2 = new Singleton2().getInstance();
System.out.println(singleton2);
}
}
}
完美中的完美 Enum 枚举实现。 来源自 《Effective Java》
直接将这个类定义为枚举类型,如果要获得Instance 就直接调用,不仅能支持多线程访问,还可以让反射机制失效,无法获得这个类。
public enum Singleton3 {
Instance
}