Loading

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

单例模式的实现非常简单,只由单个类组成。为确保单例实例的唯一性,所有的单例构造器都要被声明为私有的(private),再通过声明静态(static)方法实现全局访问获得该单例实例。

单例模式的访问方式如图所示:
Simple Pattern

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

下面介绍实现单例的两种方式:懒汉式、饿汉式。

/**
 * 懒汉式单例模式
 * 懒加载,必须加锁 synchronized 才能保证单例
 */
public class Singleton {

    public static Singleton instance;

    private Singleton() {

    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
/**
 * 饿汉式单例模式
 * 没有加锁,执行效率会提高。
 * 类加载时创建对象,消耗内存
 */
public class Singleton {

    public static Singleton instance = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return instance;
    }
}

两种方式都是将构造器 private,防止外界通过 new 来创建实例。

并通过 public static 返回一个实例。

单例模式下如何保证线程安全!

通常情况下,一个单例模式是不会出问题的,但是如果在多线程的情况下,有可能会生成多个对象。

public class Singleton {

    private Singleton() {

    }

    private static final Singleton instance = null;

    public Singleton getInstance() {
        return new Singleton();
    }
}

如果在多线程的情况下,instance 是一个共享资源,如果不进行任何保护机制的话,两个线程有可能同时进入临界区,这样这个系统会有超过一个的 Singleton 对象,单例也就失效了。想要保证在多线程下依然有用的话,就需要设置锁机制。

单例模式,线程安全的两种实现

一、拥有双重校验锁机制的同步锁单例模式( double-checked locking )

/**
 * 双重校验锁
 */
public class Singleton {

    /* 保证 instance 在所有线程中同步 */
    public static volatile Singleton instance;

    /* private 避免类在外部被实例化 */
    private Singleton() {

    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

分析

  • 第一次判断 instance 是否为null

    第一次判断实在 Synchronized 同步代码块前面判断的,由于单例模式只会创建一个实例,并通过 getInstance() 方法返回对象。所以第一次判断的原因是为了在 Singleton 对象已经被创建的情况下,避免进入同步代码块,提升效率。

  • 第二次判断 instance 是否为null

    1、假设:线程A已经经过第一次判断,判断 instance 为null,并准备进入 synchronized 代码块。

    2、此时线程B获取时间片,由于线程A还没有创建实例,所以 instance == null,这时候线程B创建了实例。

    3、此时线程A再次获取时间片,由于刚才经过 instance == null,进入同步代码块。这个时候如果不再次进行 判断,线程A又会再次创建一个实例。这样就不满足单例模式的要求,所以第二次判断很有必要!

至于为什么要加Volatile关键字,可以参考我的另一篇文章:深入理解Volatile关键字

二、无锁的线程安全单例模式


public class Singleton {

    private Singleton() {

    }

    private static final Singleton instance = new Singleton();

    public static synchronized Singleton getInstance() {
        return instance;
    }
      
}

Java中单例模式的最佳实现形式中,类只会加载一次,通过在声明时直接实例化静态成员的方式来保证一个类只有一个实例。这种实现方式避免了使用同步锁机制和判断实例是否被创建的额外检查。

If you’re going to reuse code, you need to understand that code!
posted @ 2020-11-22 13:43  不颓废青年  阅读(66)  评论(0编辑  收藏  举报