Java-单例模式

目录

前言:

1.“饿汉”模式

1.1定义

1.2实现

2.“懒汉”模式

2.1定义

2.2“懒汉模式” - 单线程版

2.3“懒汉模式” - 多线程版


序列:多线程 - 008

前言:

单例模式是比较常见的设计模式之一。

单例模式能保证某个类在程序中只存在唯一一个实例对象,而不会创建多个实例对象。

单例模式的具体的实现方式有很多,最常见的是“饿汉模式”和“懒汉模式”。下边具体介绍两种模式的使用。

1.“饿汉”模式

1.1定义

“饿汉模式”在类加载的同时,创建实例对象。

三个关键点:

  • 在类内部创建静态、私有的唯一实例对象;
  • 私有化该类的构造方法;
  • 创建外界可以访问到这个实例对象的静态方法;

1.2实现

具体的实现细节如下:

/**
 * “饿汉模式” - 在类加载的同时,创建实例对象。
 */
public class Singleton {
    private static Singleton singleton = new Singleton();//创建私有化的唯一实例对象
    //static成员,在Singleton类加载的时候,就会执行这里的创建实例的操作
    private Singleton(){
        //私有化单例类的构造方法,使外界无法再创建该类的实例对象
    }
    public static Singleton getSingleton(){//创建静态方法,外界仅能通过该方法获取到该类的实例对象
        return singleton;//外界想使用这个类的实例,只用调用这个方法即可
    }
}

注:

  1. “饿汉模式”是线程安全的,因为它只涉及到对单例对象的“读”操作;
  2. 因为“饿汉模式”是线程安全的,所以它不涉及单线程和多线程两个版本;
  3. 私有化构造方法,是为了让类外的其他地方无法再new出单例类的对象;
  4. getSingleton方法,获取创建的实例,后续想使用该类的对象,只能调用这个方法。

2.“懒汉”模式

2.1定义

“懒汉模式”在类加载的同时,不创建实例对象,第一次使用的时候才创建实例对象。

正是因为“懒汉模式”在第一次使用的时候才创建实例对象,所以“懒汉模式”涉及到对单例对象的“读”和“写”操作。

该模式对于单个线程是安全的,但是对于多个线程则会有风险,所以有两个版本的模式。

2.2“懒汉模式” - 单线程版

在“懒汉模式”的单线程版下,除了在类加载的同时不创建实例对象外,其余都相同。

if 判定表示外界第一次调用getSingleton()这个方法,才创建实例对象,后续的调用不在创建实例对象。

具体的实现代码如下:

/**
 * “懒汉模式” - 单线程版
 */
public class Singleton {
    private static Singleton singleton = null;//类加载的同时,不创建实例对象
    private Singleton(){
        //私有化单例类的构造方法,使外界无法再创建该类的实例对象
    }
    public static Singleton getSingleton(){//创建静态方法,外界仅能通过该方法创建或获取到该类的实例对象
        if (singleton == null){//外界第一次调用这个方法,才创建实例对象
            singleton = new Singleton();
        }
        return singleton;//外界想使用这个类的实例,只用调用这个方法即可
    }
}

单线程版本下“懒汉模式”是安全的,与“饿汉模式”不同仅在类加载的同时,不创建实例对象。

2.3“懒汉模式” - 多线程版

线程的的安全问题发生在首次创建实例时,如果在多个线程中同时调用getSingleton()方法,就可能导致创建出多个实例。

一旦实例已经创建好了,后边的再多线程环境调用getSingleton()方法就不会再有线程安全的问题了。所以,加上 synchronized 锁可以改善这⾥的线程安全问题。

在加锁的基础上, 做出了进⼀步改动:

  • 使⽤双重 if 判定,降低锁竞争的频率;
  • 给 instance 加上了 volatile关键字。

 具体的实现代码如下:

/**
 * “懒汉模式” - 多线程版
 */
public class Singleton {
    private volatile static Singleton singleton = null;//与饿汉模式区别,这里并没有直接操作,而是先赋值null
    //1、volatile关键字修饰唯一对象,保证代码不会出现指令重排序
    private static Singleton getSingleton(){//首次调用getInstance时,才会去创建实例(如果不调用,就不创建)

        if (singleton == null){//2、外围多加一个if判断,不能一上来直接加锁,影响代码效率,需要加锁的时候,才去加锁
            synchronized (Singleton.class){//3、把if和new合并到一个整体,线程安全的问题就得到了改善
                if (singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    private Singleton(){//私有化实例对象,使这个类只有唯一的实例
    }
}

以上的代码完美的解决了多线程编程的安全的问题。 Singleton.class表示类抽象对象。

解锁和解锁是一件比较开销比较高的事情,而“懒汉模式”的线程安全只是发生在首次创建实例的时候。因此:后续使用的时候就不用再次进行加锁了。

两个 if 判定的功能不同,外层的 if 就是判定下看当前是否已经把 singleton 实例创建出来了。

同时为了避免“内存可见性”的问题导致读取的实例对象出现偏差,于是补充上volatile关键字。

当多线程首次调用 getSingleton(),singleton实例对象为null,于是又继续往下执行来竞争锁。其中竞争成功的线程,再完成创建实例的操作。

当这个实例创建完成了之后,其他的竞争到锁的线程就会被里层的 if 判定挡住了,也就不会继续创建其他的实例了。这就是里层的 if 判定的作用。

你可能感兴趣的:(JavaEE,(初阶),单例模式,java-ee,java)