JAVA23种设计模式之单例模式

  1. 单例模式:
    单例模式涉及到一个单一的类,在系统运行中,确保这个类只有一个实例。而且这个实例是这个类自己创建的或者实例化的。也就是说这个类提供了一种访问其唯一对象的方式,可直接访问不需要实例化。单例模式是JAVA的23中设计模式中最简单的模式,但是在很多场景下需要使用单例模式。
  2. 单例模式的特定:
    • 单例类只能有一个实例
    • 单例类必须自己创建自己的唯一实例。
    • 单例类必须给所有其他对象提供这一实例。
  3. 单例类示意图:

单例模式示意图
引用自《单例模式

  1. 单例模式的示例代码:
    因为单例模式有多种实现方式,这里一一列举出来:
  • 饿汉式: 所谓饿汉式,顾名思义就是比较饥饿,所以会迫不及待的想创建对象,因此这种方式在被加载的时候就实例化创建了类。这种方式是典型的空间交换时间,就是不管这个类,在程序中是否使用,在类加载的时候就先创建出来,这样虽然节省了时间,但是会造成内存上的浪费。具体实现代码如下:
public class SingletonForLazy {
    //创建 SingletonForLazy 的一个对象(饿汉式关键代码)
    private static SingletonForLazy singleton= new SingletonForLazy();
    //私有化构造方法
    private SingletonForLazy() {
    }
    //提供获取唯一对象的方法
    public static SingletonForLazy getInstance(){
        return singleton;
    }
}
  • 懒汉式: 和饿汉式的区别是创建对象的时间不同,一般是在对象被调用的时候才会真正的实例化对象,如果没有被调用则不实例化。这一种用时间换空间的方式,是每次获取实例都先进行判断,看实例是否存在,这样的判断需要花费一定的时间,但是,如果实例存在则不需要再重复创建,这样可以减少系统内存的浪费。具体实例代码如下:
public class SingletonForHunger {
    //初始化SingletonFoHunger对象为空
    private static SingletonForHunger singleton= null;
    //私有化构造方法
    private SingletonForHunger() {
    }
    //提供获取唯一对象的方法,当外部调用该方法时开始初始化
    public static SingletonForHunger getInstance(){
        if (singleton==null) {
            singleton = new SingletonForHunger();
        }
        return singleton;
        }
}

但是以上这种方式在多线程的情况下是不安全的,会产生多个singleton对像。因为没有加锁(synchronized)。所以我们说这个从严格定义上来说不能实现单例模式。
对以上代码改造如下:

public class SingletonForSyn {
    //初始化SingletonFoHunger对象为空
    private static SingletonForSyn singleton= null;
    //私有化构造方法
    private SingletonForSyn() {
    }
    //提供获取唯一对象的方法,当外部调用该方法时开始初始化
    public   static synchronized SingletonForSyn getInstance(){
        if (singleton==null) {
            singleton = new SingletonForSyn();
        }
        return singleton;
    }

}

改造成以上这种代码后,通过synchronized的锁,使得线程变得安全,但是由于锁的特性会使得代码的效率大大降低。因此一般也不建议使用这种单例模式的实现方式。

  • 双重检查加锁: getInstance方法并不是每次都需要执行,而是先不执行,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。而且该种方法使用volatile来修饰对象。具体实例代码如下:
public class SingletonForDCL {
    //初始化SingletonFoHunger对象为空
    private volatile static SingletonForDCL singleton= null;
    //私有化构造方法
    private SingletonForDCL() {
    }
    //提供获取唯一对象的方法,当外部调用该方法时开始初始化
    public static SingletonForDCL getInstance(){
        //检查是否实例化
        if (singleton==null) {
            //线程安全的创建实例
            synchronized (SingletonForDCL.class) {
                //二次检查是否存在 不存在才正式创建实例
                if (singleton == null) {
                    singleton = new SingletonForDCL();
                }
            }
        }
        return singleton;
        }
}

双重检查加锁法是能够做到效率和安全的双重保护,但是该方法对于JDK的版本有要求,需要从JDK1.5版本开始支持,JDK1.5之前该方法是无法保证线程安全的,从网上查询资料后得知,这是因为在1.5版本后禁止指令重排优化这条语义才正常工作的。

  • 静态内部类: 这种方法能达到的效果和双重检查加锁法是一致的,但是实现起来更加简单。这种方法是利用了静态类只会加载一次的机制,使用静态内部类持有单例对象,达到单例的效果。示例代码如下:
public class SingletonForClass {
    //构建静态内部类
    private static class SingleTonHolder {
        public static final SingletonForClass singletonForClass = new SingletonForClass();
    }
    //直接返回对象
    public static SingletonForClass getInstance(){
        return SingleTonHolder.singletonForClass;
    }
}

以上代码的实现方式解释如下:

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.singletonForClass,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
引用自《JAVA与模式》之单例模式

  • 枚举法: 枚举法是现阶段实现单例模式的最优雅最简洁的方法,但是这种方法同样对于JDK的版本有要求,要求在JDK1.5版本之后。因为JDK1.5才新增enum关键字用于定义枚举类,具体示例例代码如下:
  1. 需要被实例化的对象:
public class SingleObj {
    public SingleObj() {
        System.out.println(">>>>>>>>>>>>>.create");
    }
}
  1. 枚举类:
public enum  SingletonForEnum {
    SINGLEINSTANCE;
    private SingleObj singleObj;
    private SingletonForEnum() {
        singleObj = new SingleObj();
    }
    public SingleObj getInstance() {
        return singleObj;
    }
}
  1. 测试代码类:
public class Test {
    public static void main(String[] args) {
        SingleObj singleObj = SingletonForEnum.SINGLEINSTANCE.getInstance();
    }
}

枚举法的优势在于:枚举本身无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化。因此Effective Java推荐尽可能地使用枚举来实现单例。
但是枚举法实现单例不推荐在Android平台使用,因为相比较其他方式来说,在Android平台,枚举法会消耗更多的内存。一般在Android平台比较推荐的事静态内部类或者双重检查加锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值