当前位置: 主页 > JAVA语言

java单例模式-java 单例模式 懒汉式

发布时间:2023-02-09 10:46   浏览次数:次   作者:佚名

概念

java中的单例模式是一种常见的设计模式。 单例模式分为懒惰式单例、饥饿式单例和注册式单例三种。

种类。

特征

单例模式具有以下特点:

饿了么中国风:

顾名思义,Hungry风格就是在第一次引用类的时候就创建一个对象实例,而不管它是否真的需要创建。代码如下

向下:

这是一个例子:

public class Singleton { 
    private static Singleton = new Singleton(); 
    private Singleton() {} 
    public static getSignleton(){ return singleton; }
}

这样做的好处是容易写,但是不可能耽误对象的创建。但是我们往往希望对象像

懒加载,从而减少负载,所以需要如下的懒惰风格

慵懒风格:

线程写入

公共类 Singleton { private static Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ 返回单例; }}

这种写法是最简单的。 它由一个私有构造函数和一个公共静态工厂方法组成。 工厂方法中,单例为null

判断,如果为null,则新的出来,最后返回单例对象。这种方法可以实现懒加载,但是有一个

致命弱点:线程不安全。如果两个线程同时调用getSingleton()方法,极有可能造成pair的重复创建。

大象。

public class Singleton { 
    private static Singleton singleton = null; 

java单例模式_java 单例模式 懒汉式_java 单例模式类图

private Singleton(){} public static Singleton getSingleton() { if(singleton == null) singleton = new Singleton(); return singleton; } }

考虑线程安全的写法:

这种写法考虑了线程安全,使用synchronized锁住单例和新增部分的null判断。

,使用volatile关键字来限制单例对象,保证其对所有线程可见,禁止指定

优化重新排序。 这样就可以保证这种单例模式的语义是线程安全的。注意这里说的是语义上的,实际中

实际使用中还是有小坑

public class Singleton { 
    private static volatile Singleton singleton = null; 
    private Singleton(){} 
    public static Singleton getSingleton(){ 
        synchronized (Singleton.class){ 
        if(singleton == null){ 
            singleton = new Singleton(); 
        }
    return singleton; 
    }
}

一种兼顾线程安全和效率的写法:

java 单例模式类图_java 单例模式 懒汉式_java单例模式

上面的写法虽然可以正确运行,但是效率低下,不能实际应用。因为每次调用

getSingleton()方法必须在synchronized这里排队,真正需要new的情况很少。

于是,第三种写法诞生了

public class Singleton { 
    private static volatile Singleton singleton = null; 
    private Singleton(){} 
    public static Singleton getSingleton(){ 
        if(singleton == null){ 
            synchronized (Singleton.class){ 
                if(singleton == null){ 
                    singleton = new Singleton(); 
                }
            }
        }
        return singleton; 
    }
}

这种写法称为“双重检查锁”。 顾名思义,就是在getSingleton()方法中进行两次null检查。看似很多

这一招,倒是大大提高了并发度,从而提高了性能。 为什么可以提高并发度? 如上所述,

单例中new的情况非常少,大部分都是可以并行读取的。所以加锁前多做一次null检查就可以了。

从而减少绝大多数的加锁操作,达到提高执行效率的目的

小坑:

那么,这种写法绝对安全吗? 前面说了,从语义上来说是没有问题的。但是还是有

坑。 在说这个坑之前,我们得先了解一下关键字volatile。 事实上,这个关键字有两层语义。第一层语义信念

java 单例模式类图_java单例模式_java 单例模式 懒汉式

大家比较熟悉,就是能见度。 可见性是指在一个线程中对变量的修改会立即被工作内存(Work

Memory)被写回主存(Main Memory),所以会立即反映到其他线程的读操作中。顺便说一句,work

运行内存和主存可以大致理解为实际计算机中的缓存和主存。 工作内存由线程独享,主内存由线程共享。

享受。 volatile 的二级语义是禁用指令重新排序优化。 你知道我们写的代码(尤其是多线程代码),

由于编译器优化,实际执行可能与我们写的顺序不同。编译器只保证程序执行结果与源码一致

相同,但不保证实际指令的顺序与源代码相同。 这在单线程中似乎没有问题,但是一旦引入多线程,

这种疾病会导致严重的问题。 volatile 关键字可以从语义上解决这个问题。

注意上面反复提到“在语义上没有问题”,可惜直到jdk1.5才禁止指令重排来优化这个语义

以后才能正常工作。 在以前的JDK中,即使变量声明为volatile,也无法完全避免重排序带来的问题。 地方

所以在jdk1.5版本之前,双校验锁形式的单例模式是不能保证线程安全的。

静态内部类方法:

那么,有没有一种简单的方法来延迟加载并保证线程安全呢?我们可以把Singleton实例放到一个

在静态内部类中,这避免了在Singleton类中加载静态实例时创建对象,并且因为静态内部类只

会加载一次,所以这种写法也是线程安全的:

public class Singleton { 
    private static class Holder { 
        private static Singleton singleton = new Singleton(); 
    }
    private Singleton(){} 
    public static Singleton getSingleton(){ 
        return Holder.singleton; 
    }
}

然而,上面提到的所有实现都有两个共同的缺点:

java 单例模式类图_java单例模式_java 单例模式 懒汉式

两者都需要额外的工作(Serializable、transient、readResolve())来实现序列化,否则每次反序列化一个序列化的对象实例都会创建一个新的实例。

可能有人会利用反射强行调用我们的私有构造函数(如果想避免这种情况,可以修改构造函数使其在创建第二个实例时抛出异常)。

报名类型:

注册的单例实际上维护了一组单例类的实例,并将这些实例存储在一个Map(注册表)中。 对于注册

如果实例已经通过,则直接从Map中返回,如果没有注册,则先注册,再返回。

public class Singleton { 
    private static Map map = new HashMap (); 
    static { 
        Singleton single = new Singleton(); 
        map.put(single.getClass().getName(), single); 
    }
    // 保护的默认构造子 protected Singleton() { }
    // 静态工厂方法,返还此类惟一的实例 
    public static Singleton getInstance(String name) { 
        if (name == null) { 
            name = Singleton.class.getName(); 
            System.out.println("name == null" + "--->name=" + name); 
        }
        if (map.get(name) == null) { 
            try {
                map.put(name, (Singleton) Class.forName(name).newInstance());
            } catch (InstantiationException e) { 
                e.printStackTrace(); 
            } catch (IllegalAccessException e) { 
                e.printStackTrace(); 

java 单例模式类图_java 单例模式 懒汉式_java单例模式

} catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } }

用的比较少,其实内部实现还是用的中国式单例,因为静态方法块java单例模式java单例模式,它的单例是装在类里的

它在加载时被实例化。

枚举写法:

当然单例模式还有更优雅的实现方式,就是枚举

public enum Singleton { 
    INSTANCE; 
    private String name; 
    public String getName(){ return name; }
    public void setName(String name){ this.name = name; }
}

使用枚举提供了一种自动序列化机制,除了线程安全和防止反射强行调用构造函数外,还可以防止反序列化

创建新对象时。 所以建议尽量使用枚举来实现单例。

最后,无论采取哪种方案,请时刻牢记单例的三个要点:

线程安全

延迟加载

序列化和反序列化安全

终于

文末,笔者为大家整理了很多资料! 包含java核心知识点+全套架构师学习资料和视频+一线大厂面试书+面试简历模板+阿里美团网易腾讯小米爱奇艺快手bilibili面试题+Spring源码宝典+Java架构实战电子书ETC!