单例设计模式不同实现(秀恩爱版)

Java设计模式—单例设计模式(秀恩爱版)

对于系统中的某些类来说,只有一个实例很重要。就像男朋友(类)只有一个ChaoWang(对象)。

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

实现:

①. 私有化类的构造器

②. 提供一个自身的静态私有成员变量

③. 提供公共静态方法,返回当前类的对象。

通常单例模式在Java语言中,有两种构建方式:

  • 懒汉式。指全局的单例实例在第一次被使用时构建
  • 饿汉式。指全局的单例实例在类装载时构建实例在整个程序周期都存在

单例模式有很多种写法,大部分写法都或多或少有一些不足。下面将分别对这几种写法进行介绍。

1.饿汉式

public class BoyFriend {
//①.私有化构造器
private BoyFriend(){
}
//②.提供静态私有成员变量(我的男朋友ChaoW)
public static BoyFriend ChaoW = new BoyFriend();
//③. 提供公共静态方法,返回当前类的对象。
public static BoyFriend getName(){
return ChaoW;
}
}

饿汉式的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。但它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

2.懒汉式

public class BoyFriend {
private BoyFriend(){
}
public static BoyFriend ChaoW;
public static BoyFriend getName(){
//还未创建过对象
if(ChaoW == null){
ChaoW = new BoyFriend();
}
return ChaoW;
}
}

懒汉式单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。但缺点是此写法不安全,如果两个线程同时运行到判断ChaoW是否为null的if语句,并且ChaoW的确没有被创建时,那么两个线程都会创建一个实例,此时类型BoyFriend就不再满足单例模式的要求了。

3.懒汉式改进(加锁)

public class BoyFriend {
private BoyFriend(){
}
public static BoyFriend ChaoW;
public static synchronized BoyFriend getName(){
if(ChaoW == null){ //Single Checked
ChaoW = new BoyFriend();
}
return ChaoW;
}
}

但这样每次调用getName()方法时都被synchronized关键字锁住,可能会引起线程阻塞,影响程序的性能。

4.懒汉式改进(双重检验锁)

为了在多线程环境下,不影响程序的性能,不让线程每次调用getName()方法时都加锁,而只是在实例未被创建时再加锁,在加锁处理里面还需要判断一次实例是否已存在

public class BoyFriend {
private BoyFriend(){
}
public static BoyFriend ChaoW;
public static BoyFriend getName(){
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
if(ChaoW == null){
synchronized (BoyFriend.class) {
if(ChaoW == null){
ChaoW = new BoyFriend();
}
}
}
return ChaoW;
}
}

可以看到上面在同步代码块外多了一层ChaoW为空的判断。由于单例对象只需要创建一次,如果后面再次调用getName()只需要直接返回单例对象。因此,大部分情况下,调用getName()都不会执行到同步代码块,从而提高了程序性能。

5.静态内部类

加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。

public class BoyFriend {
private BoyFriend(){
}
/**
* 一个私有的静态内部类,用于初始化一个静态final实例
*/
private static class BoyFriendHolder {
private static final BoyFriend ChaoW = new BoyFriend();
}
public static BoyFriend getName(){
return BoyFriendHolder.ChaoW;
}
}

这种方式同样利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全

6.枚举

public class BoyFriend {
}
enum MyLove{
LOVE;
private BoyFriend ChaoW;
private MyLove(){
ChaoW = new BoyFriend();
}
public BoyFriend getName(){
return ChaoW;
}
}

获取实例的方式很简单,MyLove.LOVE.getName()

单例是如何被保证的:

  • 首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法。
  • 同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。
  • 也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的LOVE也被保证实例化一次。

单例模式的线程安全性

单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。总结一下:

(1)饿汉式:线程安全

(2)懒汉式:非线程安全

(3)双检锁:线程安全

(4)静态内部类:线程安全

(5)枚举:线程安全

------ 本文结束感谢您的阅读 ------