设计模式之单例模式

本文详细介绍了Java中的三种单例模式实现方式:饿汉式、懒汉式和静态内部类。饿汉式在类加载时即完成初始化,保证线程安全但可能造成资源浪费;懒汉式延迟初始化,但在多线程环境下需要同步处理,防止多个实例创建。通过双重检查锁定和volatile关键字可以解决懒汉式的线程安全问题。静态内部类方式利用类加载机制保证单例,同时避免了同步开销。文章深入探讨了单例模式在并发环境下的实现细节和优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说到单例模式,这个也是面试中经常需要考察的一点,而单例模式最重要的一点就是构造器私有,在构造器私有的情况下,我们不能直接new 获得对象,而是应该通过它 提供的方法进行对象的获取。有时面试官还会让你手写单例模式考察你的编码能力。而关于单例模式的实现分为很多种方法,比如有饿汉式单例、懒汉式单例、静态内部类。
以下分别写出这三种单例模式的实现方法。

饿汉式单例

概念:饿汉式,从这个饿字中我们可以看出,这个人十分的饿,在一进来就对对象进行初始化创建,而不是在需要使用的时候才对对象进行创建。代码如下:

public class Hungry{
    private static final Hungry hungry= new Hungry();
    private Hungry(){
        System.out.println(Thread.currentThread()+"------>创建单例模式");
    }
    public static Hungry getInstance(){
        return hungry;
    }
}

总结:从代码中可以看到,在这个类中,我们直接一开始就对对象进行了初始化创建,所以我们可以不用担心在多线程情况下产生问题,但是在我们没有使用该对象的时候,会造成资源浪费。

懒汉式单例

概念:相对于饿汉式,懒汉式这个懒字我们可以看出,这个人十分的懒,只有在我们需要使用该对象的时候才对这个对象进行创建。而不用担心造成资源浪费。
代码如下:

public class Lazy{
    private static  Lazy lazy;
    private Lazy(){
        System.out.println(Thread.currentThread()+"创建饿汉式单例");
    }
    public static Lazy getInstance(){
        if(lazy==null){
            lazy = new Lazy();
        }
        return lazy;
    }
}    

这个代码在单线程的测试情况下,我们返回的对象的hashcode都是一样的,但是在多线程情况下的话,就会出现情况,是因为在一个线程还没有执行完创建对象的时候,另一个线程也进入了,而这个时候lazy也是null,所以另一个线程认为这个对象还没有被创建,所以同样进入了创建对象的过程。所以会出现这个代码的在多线程执行的情况下出现返回的对象不一致的情况。

如何解决?
我们可以在执行getInstance代码的时候,执行两次检测,首先判断lazy是否为空,然后对Lazy.class进行上锁。然后在在进行是否为空然后选择是否创建的过程。
基本代码如下:

public class Lazy{
    private static  Lazy lazy;
    private Lazy(){
        System.out.println(Thread.currentThread()+"创建饿汉式单例");
    }
    public static Lazy getInstance(){
        //这个判断在之前是否有线程执行完了创建对象。如果有直接返回对象,如果没有则对Lazy.class进行上锁。然后进行判断。   
        if(lazy==null){
            synchronized (Lazy.class){
                if(lazy==null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
}

这个代码在多线程测试的情况下,可以通过。
但是这个真的没有问题了吗?
首先 在对new Lazy()的过程中,这个过程并不是一个原子性操作,所谓原子性操作,就是在计算机底层并不是一次就能执行成功,需要分步骤进行执行,比如创建一个对象的过程就分为以下三步。

  1. 为对象分配内存空间
  2. 执行构造方法,初始化对象
  3. 将初始化的对象指向这个空间。

在计算机底层是存在指令重排的现象的,指令重排也就是计算机会对你的代码进行优化,而不是一直按照你的顺序进行执行。
以上三步的最佳执行顺序就是 1 2 3,但是在极端的情况下是会出现1 3 2的情况的,
比如在线程A执行的时候,它按照1 3 2 的顺序进行执行,当执行到3 的时候,也就是分配的内存空间已经被指向了,当时这个时候还没有被赋值,但是线程B认为这个时候的对象已经不等于null了,所以选择执行直接返回lazy,但是这个情况下,lazy其实还没有执行构造方法进行赋值。所以产生了问题。
如何解决?
通过对对象添加volatile,可以禁止对这个对象进行指令重排,那么计算机就会按照1 2 3 的顺序进行执行。不会出现错误。所以修改后的代码如下:

public class Lazy{
    private volatile static  Lazy lazy;
    private Lazy(){
        System.out.println(Thread.currentThread()+"创建饿汉式单例");
    }
    public static Lazy getInstance(){
        if(lazy==null){
            synchronized (Lazy.class){
                if(lazy==null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
}

静态内部类

概念:使用静态内部类的static方法对外部类也就是我们的单例初始化,同样可以实现单例模式。

public class Holder {
    private Holder(){
        System.out.println("静态内部类实现单例模式的构造方法");
    }
    public static Holder getInstance(){
        return Inner.holder;
    }
    public static class Inner{
        private static final Holder holder = new Holder();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值