Java 单例设计模式的5种写法及优缺点

单例设计模式的定义:保证在整个系统中,一个类只存在一个实例的设计模式就是单例设计模式。

接地气的讲单例的好处就是可以少new对象,减少内存的垃圾,缩短GC(垃圾回收)的时间。

Java中实现单例的5种方法 分别有 饿汉,懒汉,DCL,静态内部类,枚举,接下来依次实现和讲解优缺点。

1.饿汉

 饿汉的实现首先要有一个private的构造函数 然后 创建一个静态的对象 在通过内部静态的get方法return 出去 代码如下:

public class HungurySingleton {


    private static final HungurySingleton mHungurySinglenton = new HungurySingleton(); //静态内部对象
    private HungurySingleton(){ //private的构造函数
        System.out.println("单例创建成功");

    }
    public static HungurySingleton getmHungurySinglenton(){ //静态的内部get方法
        return mHungurySinglenton;
    }
    public static void createString(){
        System.out.println("在单例中创建实例");
    }

    public static void main(String[] args) {
        HungurySingleton.createString();
    }


}

饿汉的优点  就是实现了单个线程的最基本的单例设计模式。

饿汉的缺点  就是无法进行 延时加载(只有在真正需要数据的时候,才进行数据加载操作。),也就是不管你用不用这个单例类,他都会实现内部的构造方法,同时他也不是线程安全的。

2.懒汉(有线程锁的方式)

懒汉的实现方法主要是饿汉的升级版,他在饿汉的基础上进行了判空操作,代码如下:

public class LazySingleton {

    private static LazySingleton lazySingleton;
    private LazySingleton(){

    }
    public static synchronized LazySingleton getInstanse(){
        if (lazySingleton  == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

    public  static void createString(){
        System.out.println("Create String");
    }
}

同时,给get方法,加上了线程锁,根据饿汉我们知道,饿汉是线程不安全的,这是因为当多个线程实现单例类的时候,另一个线程要是也在创建,就会创建出两个实例,这跟我们的定义不符。

加锁懒汉的优点 在饿汉的基础上实现了延时操作,这样我们就可以弥补饿汉的不足。同时我们加了线程锁,可以实现多个线程都是用这一个对象,解决了线程安全问题。

加锁懒汉的缺点 synchronized锁会影响性能,例如在一个线程进行单例类的时候的时候,先会判断这个加锁的类或者方法有没有在用,如果再用要等到用完,才进行使用,这也导致了性能变慢。


3.DCL (双重锁)

DCL是对加锁懒汉的优化,在判空操作之后在加锁,在判空然后进行对象的创建代码如下:

public class LazyDCL {


    private static volatile LazyDCL mInstance = null;

    private LazyDCL(){}

    public void doSometing(){
        System.out.println("XD");
    }
    public static LazyDCL getInstance(){
        if(mInstance == null){
            synchronized (LazyDCL.class){
                if(mInstance == null){
                    mInstance = new LazyDCL();
                }
            }
        }
        return mInstance;
    }

}

下面我们要说一下为什么要给对象加上volatile修饰符,同时也是DCL的缺点,可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了,同时因为实例化对象不是一个原子操作 不是原子操作就会对对象分配内存,然后让对象指向内存,等操作...  但是JVM虚拟机有指令重排序的优化,也就是将上述的步骤不按顺序进行执行,这会 导致空指针等异常,也会导致线程的不安全,所以我们加上了volatile来禁止指令重排序。

PS:Volatile不会保证原子性。 他也会讲每一次改变记录在主存中,读取从主存中读取。这同时也保证了线程的安全。

DCL的优点就是:懒加载+线程安全。

4.静态内部类

静态内部类的写法主要是通过单例类的内部初始化一个静态内部类,在内部类里初始化一个static fianl的对象(初始化之后无法被修改) 所以也是线程安全的,同时实现了单例 代码如下:

public class StaticInnerSingleton {
    //使用静态内部类进行单例模式
    private StaticInnerSingleton(){}

    public static StaticInnerSingleton getInstance(){
        return SingletonHolder.sInstance;
    }
    private static class SingletonHolder{
        private static final StaticInnerSingleton sInstance = new StaticInnerSingleton();
    }
}

静态内部类的优点 因为内部类是私有的,所以除了get方法外,没有办法访问它,同时static和final是JVM本身的机制保证了线程的安全,同时它在性能上也没有损耗。

5.枚举类

在Java5之后引入了枚举类的概念,它也是定义一种常量的方式,相比如final static 提供了更多的方法,其中就有INSTANCE;这个关键字就是单例。代码如下:

public enum enumSingleton {
    INSTANCE;

    public void doSomeThing(){
        System.out.println("XD");
    }

}
优点:实现简单,同时包括静态内部类的优点。


以上就是我对单例设计模式的总结,如果有不对的地方或者遗漏的地方还请指出!

猜你喜欢

转载自blog.csdn.net/qq_40033365/article/details/79868169