Java单例的8种实现方式及优缺点详解

前言

单例模式作为开发过程中最常用的设计模式之一,是程序员必须了解和掌握的技能,但是单例的实现方式和手段有很多种,每一种都有着各自的优缺点,如何在代码中运用就需要我们对每一种实现方式都了如执掌方可运筹帷幄。 单例的实现方式

一、静态常量饿汉式

代码

public class Single {
    private static Single single = new Single();
    
    // 一定要将默认构造方法设置为private 否则反射实例化将破坏单例
    private Single() {
    }

    public static Single getInstance() {
        return single;
    }
}
  • 优点
  • 线程安全;
  • 实现起来容易,代码容易理解;
  • 没有锁存在,执行性能高;

  • 缺点

  • 没有实现懒加载,可能产生垃圾对象,如果能确定这个对象必定会使用,可以考虑这种方式

  • 存在构造方式未设置private而导致反射实例化破坏单例的风险

二、静态代码块饿汉式

代码

public class Single {
    private static Single single = null;
    
    static{
      single = new Single();
    }
    
    // 一定要将默认构造方法设置为private 否则反射实例化将破坏单例
    private Single() {
    }

    public static Single getInstance() {
        return single;
    }
}
  • 优点
  • 线程安全;
  • 实现起来容易,代码容易理解;
  • 没有锁存在,执行性能高

  • 缺点

  • 没有实现懒加载,可能产生垃圾对象,如果能确定这个对象必定会使用,可以考虑这种方式

  • 存在构造方式未设置private而导致反射实例化破坏单例的风险

三、懒汉式一(不要使用)

代码

public class Single {

    private static Single single = null;
    
    // 一定要将默认构造方法设置为private 否则反射实例化将破坏单例
    private Single() {
    }

    private static Single getInstance() {
        // 当多个线程同时执行带这里的时候,会创建出多个对象
        if (null == single) {
            single = new Single();
        }
        return single;
    }
}
  • 优点
  • 实现了懒加载;
  • 实现起来容易,代码容易理解;
  • 缺点
  • 非线程安全,无法保证单例,所以,不要使用。
  • 存在构造方式如果未设置private而导致反射实例化破坏单例的风险

四、懒汉式二(不推荐使用)

对象实例方法加synchronized;确保线程安全下,只有一个线程调用实例化。 代码

public class Single {

    private static Single single = null;

    // 一定要将默认构造方法设置为private 否则反射实例化将破坏单例
    private Single() {
    }

    private static synchronized Single getInstance() {
        if (null == single) {
            single = new Single();
        }

        return single;
    }
}
  • 优点
  • 实现了懒加载;
  • 线程安全
  • 实现起来容易,代码容易理解;
  • 缺点
  • 锁粒度太粗,导致执行的性能会下降
  • 存在构造方式如果未设置private而导致反射实例化破坏单例的风险

五、懒汉式三(不要使用)

代码

public class Single {

    private static Single single = null;

    // 一定要将默认构造方法设置为private 否则反射实例化将破坏单例
    private Single() {
    }

    private static Single getInstance() {
        if (null == single) {
            // 当多个线程通知执行到这里来了之后,无法保证对象单一
            synchronized(Single.class){
                single = new Single();
            }
        }

        return single;
    }
}
  • 优点
  • 实现了懒加载;
  • 实现难度一般,代码容易理解;
  • 缺点
  • 非线程安全,无法保证对象单一,所以,不要使用。
  • 存在构造方式如果未设置private而导致反射实例化破坏单例的风险

六、双重检查DCL(推荐使用)

这里为什么一定要加volatile;可阅读:面试官:双重检查单例(DCL)要不要加volatile?详解来了 代码

public class Single {
    //这里一定要加volatile 否则可能因为指令重排的问题导致对象未初始化完成的情况
    private volatile static Single single = null;

    // 一定要将默认构造方法设置为private 否则反射实例化将破坏单例
    private Single() {
    }

    public static Single getInstance() {
        if (null == single) {
            synchronized (Single.class) {
                if (null == single) {
                    single4 = new Single();
                }
            }
        }
        return single;
    }
}
  • 优点
  • 实现了懒加载;
  • 线程安全
  • 锁粒度较细,只有第一次初始化的时候会走到synchronized部分
  • 缺点 实现起来相对复杂,对于volatile的理解会比较的难 存在构造方式如果未设置private而导致反射实例化破坏单例的风险

七、静态内部类(推荐使用)

代码(内部类的方式)

public class Single {

    // 一定要将默认构造方法设置为private 否则反射实例化将破坏单例
    private Single() {
    }

    private static class LazyHolder {
        private static final Single INSTANCE = new Single();
    }

    public static Single getInstance() {
        return LazyHolder.INSTANCE;
    }
}
  • 优点
  • 实现了懒加载;
  • 线程安全
  • 没有锁,性能较好
  • 缺点
  • 实现及理解起来相对复杂
  • 存在构造方式如果未设置private而导致反射实例化破坏单例的风险

八、枚举单例(推荐使用)

代码

public enum Single
{
    INSTANCE;

    public void test()
    {
        System.out.println("test()");
    }
}
  • 优点
  • 线程安全;
  • 没有锁,性能较好;
  • 实现简单,理解容易;
  • 不会因构造方法未设置为private而带来的破坏单例的风险.
  • 缺点 未实现了懒加载;

总结

以上列举了开发过程中所有的单例实现方式,同时还列出了详细的其详细的优缺点;枚举方式、DCL方式以及静态内部类的方式是个人相对比较推荐的方式;可以视情况使用;至于饿汉式的两种写法,由于比较简单,如果对应的单例对象在系统中必定用到而且频繁使用,也可以考虑使用;

所以,没有最好,只有更好!合适才好!!!

Comments: 1

「人生在世,留句话给我吧」

提交评论