java笔试题:实现单例模式

单例模式的实现

  • java单例模式的实现分为五种:

    • 饿汉模式(线程安全)
    • 懒汉模式(线程不安全,但是可以修改,不建议使用,延迟加载)
    • DCL双检查锁机制(线程安全,延迟加载)
    • 静态内部类实现(线程安全,但是遇见序列化对象时会出现多实例情况,延迟加载)
    • 枚举类实现(线程安全,不会出现序列化带来的问题,延迟加载)

1. 饿汉模式

  • 优点:单例在类被加载的时候被创建,线程安全,可以用于多线程环境
  • 缺点:如果单例未被使用,那么同样会被创建,会造成内存浪费
/**
 * 饿汉模式
 * 在调用前就已经创建,可能会浪费内存
 */
public class Singleton1 {

    //设置为私有 防止其他对象调用
    private Singleton1 (){};

    private static Singleton1 instance = new Singleton1();

    public static Singleton1 getInstance(){
        return instance;
    }

}

2. 懒汉模式

  • 优点:在需要被使用的时候才被创建,不会造成资源浪费
  • 缺点:线程不安全,getInstance方法没有同步,在多线程的情况下会造成创建的实例不同
  • 线程安全的修改方法:
    • 使用synchronized关键字同步getInstance方法
    • 使用synchronized关键字同步代码块

但是这两种修改方法会使代码的效率变的很低,所以不推荐使用

/**
 * 懒汉模式  非线程安全  通过添加synchronized关键字来同步getInstance方法
 * 解决懒汉模式:同步getInstance方法  同步代码块
 */
public class Singleton2 {

    private Singleton2(){};

    private static Singleton2 instance = null;

    public static Singleton2 getInstance(){
        if(instance == null){
           instance = new Singleton2();
        }
        return instance;
    }
}

3. DCL双检查锁机制

  • 线程安全,延时加载

  • 双检查锁机制就是进行第二次检查,也就是代码中两次判断null的操作,但是为什么要这样呢?

  • 特殊情况的出现

    • 线程ThreadA,ThreadB,如果threadA执行到了第一个if条件判断,instance = null;ThreadB也执行到了if条件判断instance = null,所以A和B会依次执行同步代码块里的代码。为了避免创建两个实例,因此又在同步代码块里添加了if条件进行二重检验。
  • 但是由于会出现指令重排序的问题,这样还会导致线程不安全的情况,所以添加了volatile 关键字

  • volatile 关键字作用:

    • 禁止指令重排序
    • 同步内存,每次修改值之后必须立即刷新到主存,线程取的时候也是从主存取
/**
 - 双重锁机制
 - 要注意判断两次null
 */
public class Singleton3 {

    private Singleton3(){};

    public volatile static Singleton3 instance = null;

    public static Singleton3 getInstance(){
        if(instance == null){

            synchronized (Singleton3.class){
                if(instance == null){
                    instance = new Singleton3();
                }

            }
        }
        return instance;
    }
}

4. 静态内部类实现

  • 线程安全,延时加载
  • 由于内部类是隐藏的,所以只要不使用内部类,那么JVM就不会将其加载进来,这样就很好的实现了延迟加载
  • 缺点:遇到序列化对象的时候,如果还使用默认的运行方式,就会出现多实例的情况,解决方法是在反序列化中使用readResolve方法
  • 为什么是线程安全的:虚拟机会保证一个类的构造器方法在多线程环境中被正确地加载,同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的
public class Singleton4 {

    private Singleton4(){};
    private static class SingleHolder{
        private static Singleton4 instance = new Singleton4();
    }

    public static Singleton4 getInstance(){
        return SingleHolder.instance;
    }
}

5. 枚举类实现

  • 由于很不常见,所以了解即可
 public enum ExSingleton {  
     INSTANCE;  
     public void someMethod() {  
     }  
 } 

6.总结

  • 总的来说双重锁机制和静态内部类机制是推荐使用的,双重锁机制是一般多线程情况下的实现选择

  • 在面试的时候可以选择写静态内部类实现和DCL双重锁机制实现,但是书写的过程都是类似的:

    • DCL双重锁:

      • 首先声明私有的构造函数
      • 再定义私有的静态实例,并且使用 volatile 关键字修饰
      • 实现getInstance静态方法,先进行第一次检查,之后实现synchronized同步代码块,在其中实现第二次检查,进行instance赋值
      • 最后return instance
    • 静态内部类:

      • 首先声明私有构造函数
      • 再定义静态内部类,其中定义私有静态实例并创建
      • 实现getInstance静态方法,返回静态内部类中的实例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值