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

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

一、饿汉式

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

实现方式

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
相关推荐
认真的酒窝1 天前
自己动手开发编译器(十一)语义分析
java·开发语言
wbs_scy1 天前
Linux C++ 高并发编程:线程池全链路深度解析,从原理到手撕实现
java·开发语言
JAVA面经实录9171 天前
Linux 常用命令完整知识体系
java·linux·开发语言·汇编
贪玩的蛋挞1 天前
C#与闭包
开发语言·c#
端庄的战斗机1 天前
javascript 设计模式(文章很长,请自备瓜子,水果和眼药水)
开发语言·javascript·设计模式
Full Stack Developme1 天前
Java LRU 与 LFU 算法及应用
java·开发语言·算法
C语言小火车1 天前
C++ 堆排序深度精讲:基于完全二叉树的选择排序进化,最坏情况 O(n log n) 的稳定王者
开发语言·c++·算法·排序算法·堆排序
北冥you鱼1 天前
abigen 最佳实践:从入门到精通,高效生成 Go 语言合约绑定
开发语言·golang·区块链
程序员老油条1 天前
WSL2 + Docker Desktop:Windows 下的完美 Java 开发环境
java·windows·docker·wsl2
ALex_zry1 天前
C++26 std::complex 结构化绑定详解:auto [re, im] = c
c语言·开发语言·c++