【设计模式】23种设计模式-单例模式
在程序设计中,很多时候我们只需要一个全局实例,如:Windows中的任务管理器、数据库连接池、线程池等等都是使用的是单例模式。
实现单例模式(Singleton)的方式有很多种。
实现单例需要考虑以下情况:
1、对象实例由谁来创建:不能交给他人,那么只能有对象自己去创建,所以构造方法需设置为私有private
2、多线程环境下能否保证只有一个实例
下面学习下常见的几种实现方式
饿汉式实现:
public class SingletonTest {// 只要类被加载到JVM就创建一个实例private static SingletonTest instance = new SingletonTest();// 私有构造保证实例不能再被别人创建private SingletonTest(){}// 向外界提供一个获取唯一实例的接口public static SingletonTest getInstance(){return instance;}}
public static void main(String[] args) {SingletonTest instance1 = SingletonTest.getInstance();SingletonTest instance2 = SingletonTest.getInstance();System.out.println(instance1 == instance2); // true}
懒汉式实现
public class SingletonTest02 {// 声明一个实例private static SingletonTest02 instance;// 私有构造保证实例不能再被别人创建private SingletonTest02(){}// 向外界提供一个获取唯一实例的接口public static SingletonTest02 getInstance(){if (instance == null){try {Thread.sleep(1);} catch (InterruptedException e) {}// 调用时再去创建实例instance = new SingletonTest02();}return instance;}public static void main(String[] args) {for (int i=0; i<100; i++){new Thread(() -> {System.out.println(SingletonTest02.getInstance().hashCode());}).start();}}}
测试:启动100个线程同时去获取实例,打印其hashcode。结果如下
1406241281140624128114062412811406241281898323977139505508139505508103512977
hashcode不一样说明多线程环境下该写法产生了多个实例,与主题违背
改进的懒汉式
改进方案:同步锁+双重判断
// 向外界提供一个获取唯一实例的接口public static SingletonTest03 getInstance(){// 第一次判断if (instance == null){try {Thread.sleep(1);} catch (InterruptedException e) {}// 调用时再去创建实例synchronized(SingletonTest03.class){// 二次判断if (instance == null){instance = new SingletonTest03();}return instance;}}return instance;}
说明:
第一个为空判断有人觉得可以去掉,去掉后从线程安全方面来说是没有线程安全问题的,但是从执行效率上并不是最好的,去掉的话有多少个线程就要持有多少个锁,效率低下。
public class SingletonTest04 {// 私有构造保证实例不能再被别人创建private SingletonTest04(){}// 私有内部类private static class Inner{// 内部类中声明一个实例private static final SingletonTest04 instance = new SingletonTest04();}// 向外界提供一个获取唯一实例的接口public static SingletonTest04 getInstance(){return Inner.instance;}public static void main(String[] args) {for (int i=0; i<100; i++){new Thread(() -> {System.out.println(SingletonTest04.getInstance().hashCode());}).start();}}}
有了这么多单模式的实现,开发Java语言的作者之一看不下去了,直接一招解决问题
枚举实现
public enum SingletonTest05 {instance;public static void main(String[] args) {for (int i=0; i<100; i++){new Thread(() -> {System.out.println(SingletonTest05.instance.hashCode());}).start();}}}
用枚举实现单例的优势:
1、保证线程安全
2、防止反序列化创建对象
以上是常见的实现单例实例的实现方法,各有优缺点,然而在实际设计单例过程中,考虑的问题不仅仅只有线程安全方面,如常见的分布式集群(多JVM进程)下是否是对象实例是否是单例、反序列化后被恶意创建实例等问题。
往期精彩回顾
