剑指offer:2. 实现Singleton模式,从简单实现到考虑内存及并发多种实现

李俊
• 阅读 908

题目

设计一个类,我们只能生成该类的一个实例

分析

最简单实现

  • 私有构造方法
  • 静态方法获取实例

是否需要考虑内存或并发环境

如果需要考虑内存,使用到时才创建实例对象(饿汉),不使用时就不创建实例(懒汉,懒加载)。

如果需要考虑线程安全,就要确保获取实例是同步的,避免创建多个实例。

实现方式

  • [x] 1. 单线程(懒汉式、饿汉式)
  • [x] 2. 多线程工作效率不高(加锁获取实例的方法)
  • [x] 3. 加同步锁前后两次判断实例是否已存在
  • [x] 4. 利用静态初始化创建实例(推荐,线程安全,会占用一部分内存)
  • [x] 5. 利用静态内部类实现按需创建实例(最推荐,线程安全,效率高,聪明的你应该可以明白的)

编码实现

1. 单线程(懒汉式、饿汉式)

饿汉单例

package cn.jast.java.offer.singleton;

/**
 * 简单饿汉单例
 *
 */
public class SimpleHungerSingleton {

    private static SimpleHungerSingleton simpleSingleton;

    private SimpleHungerSingleton(){
        simpleSingleton = new SimpleHungerSingleton();
    }

    public static SimpleHungerSingleton getInstance(){
        return simpleSingleton;
    }

}

简单懒汉单例

package cn.jast.java.offer.singleton;

/**
 * 简单懒汉单例
 * 
 */
public class SimpleLazySingleton {

    private static SimpleLazySingleton simpleSingleton;

    private SimpleLazySingleton(){

    }

    public static SimpleLazySingleton getInstance(){
        if(simpleSingleton == null){
            simpleSingleton = new SimpleLazySingleton();
        }
        return simpleSingleton;
    }

}

线程安全测试

/**
     * 测试简单单例的线程安全
     */
    public static void testSimpleLazySingleton(){
        Set<SimpleLazySingleton> singletonSet = Collections.synchronizedSet(new HashSet<>());
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                SimpleLazySingleton simpleLazySingleton = SimpleLazySingleton.getInstance();
                singletonSet.add(simpleLazySingleton);
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isShutdown()){
                if(singletonSet.size()>1){
                    System.out.println("简单单例存在创建多个实例对象,实例如下:");
                    System.out.println(singletonSet);
                }
                break;
            }

        }
    }

输出:

简单单例存在创建多个实例对象,实例如下:
[cn.jast.java.offer.singleton.SimpleLazySingleton@2b9283d, cn.jast.java.offer.singleton.SimpleLazySingleton@72fba635]

2. 多线程工作效率不高(加锁获取实例的方法)

package cn.jast.java.offer.singleton;

public class Synchronized1Singleton {

    private static Synchronized1Singleton instance;

    private Synchronized1Singleton(){

    }

    /**
     * 每次获取对象时都加锁来确保创建对象
     * @return
     */
    public static synchronized Synchronized1Singleton getInstance(){
        if(instance == null){
            instance = new Synchronized1Singleton();
        }
        return instance;
    }
}

测试:

public static void testSynchronized1Singleton(){
        long startTime = System.currentTimeMillis();
        Set<Synchronized1Singleton> singletonSet = Collections.synchronizedSet(new HashSet<>());
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                Synchronized1Singleton singleton = Synchronized1Singleton.getInstance();
                singletonSet.add(singleton);
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isShutdown()){
                System.out.println(String.format("执行时间:%s ms",System.currentTimeMillis()-startTime));
                if(singletonSet.size()>1){
                    System.out.println("简单单例存在创建多个实例对象,实例如下:");
                    System.out.println(singletonSet);
                }
                break;
            }

        }
    }

输出:

执行时间:72 ms(注:一个样例)

3. 加同步锁前后两次判断实例是否已存在

package cn.jast.java.offer.singleton;

public class Synchronized2Singleton {

    private static Synchronized2Singleton instance;

    private Synchronized2Singleton(){

    }

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

}

4. 利用静态初始化创建实例(推荐,线程安全

package cn.jast.java.offer.singleton;

/**
 * 推荐
 */
public class StaticInitializeSingleton {

    private static StaticInitializeSingleton instance ;

    static{
        instance = new StaticInitializeSingleton();
    }

    private StaticInitializeSingleton(){

    }

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

5. 利用静态内部类实现按需创建实例(最推荐,线程安全,效率高

package cn.jast.java.offer.singleton;

/**
 * 推荐
 */
public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton(){

    }

    public static StaticInnerClassSingleton getInstance(){
        return Inner.instance;
    }


    private static class Inner{
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }
}

完整的测试

package cn.jast.java.offer.singleton;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

    public static void main(String[] args) {
//        testSimpleLazySingleton();
        testSynchronized1Singleton();
//        testSynchronized2Singleton();
//        testStaticInitializeSingleton();
//        testNestedClassSingleton();
    }


    /**
     * 测试简单单例的线程安全
     */
    public static void testSimpleLazySingleton(){
        Set<SimpleLazySingleton> singletonSet = Collections.synchronizedSet(new HashSet<>());
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                SimpleLazySingleton simpleLazySingleton = SimpleLazySingleton.getInstance();
                singletonSet.add(simpleLazySingleton);
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isShutdown()){
                if(singletonSet.size()>1){
                    System.out.println("简单单例存在创建多个实例对象,实例如下:");
                    System.out.println(singletonSet);
                }
                break;
            }

        }
    }

    /**
     * 测试线程安全的单例模式实现
     */
    public static void testSynchronized1Singleton(){
        long startTime = System.currentTimeMillis();
        Set<Synchronized1Singleton> singletonSet = Collections.synchronizedSet(new HashSet<>());
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                Synchronized1Singleton singleton = Synchronized1Singleton.getInstance();
                singletonSet.add(singleton);
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isShutdown()){
                System.out.println(String.format("执行时间:%s ms",System.currentTimeMillis()-startTime));
                if(singletonSet.size()>1){
                    System.out.println("简单单例存在创建多个实例对象,实例如下:");
                    System.out.println(singletonSet);
                }
                break;
            }

        }
    }

    /**
     * Synchronized2Singleton 的效率比 Synchronized1Singleton高几倍甚至几十倍以上
     */
    public static void testSynchronized2Singleton(){
        long startTime = System.currentTimeMillis();
        Set<Synchronized2Singleton> singletonSet = Collections.synchronizedSet(new HashSet<>());
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                Synchronized2Singleton singleton = Synchronized2Singleton.getInstance();
                singletonSet.add(singleton);
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isShutdown()){
                System.out.println(String.format("执行时间:%s ms",System.currentTimeMillis()-startTime));
                if(singletonSet.size()>1){
                    System.out.println("简单单例存在创建多个实例对象,实例如下:");
                    System.out.println(singletonSet);
                }
                break;
            }

        }
    }

    /**
     *
     */
    public static void testStaticInitializeSingleton(){
        Set<Synchronized2Singleton> singletonSet = Collections.synchronizedSet(new HashSet<>());
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                Synchronized2Singleton singleton = Synchronized2Singleton.getInstance();
                singletonSet.add(singleton);
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isShutdown()){
                if(singletonSet.size()>1){
                    System.out.println("简单单例存在创建多个实例对象,实例如下:");
                    System.out.println(singletonSet);
                }
                break;
            }

        }
    }

    public static void testNestedClassSingleton(){
        Set<StaticInnerClassSingleton> singletonSet = Collections.synchronizedSet(new HashSet<>());
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            executorService.submit(()->{
                StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance();
                singletonSet.add(singleton);
            });
        }
        executorService.shutdown();
        while(true){
            if(executorService.isShutdown()){
                if(singletonSet.size()>1){
                    System.out.println("简单单例存在创建多个实例对象,实例如下:");
                    System.out.println(singletonSet);
                }
                break;
            }

        }
    }
}

自问自答

问:单例模式获取实例的方法为什么是静态方法?
答:因为构造方法是私有的,无法通过new创建实例,那只能通过类方法获取实例。那通过反射是否可以创建实例呢?

知识点延伸

Java创建实例对象的方式有哪些?

参考

点赞
收藏
评论区
推荐文章
3A网络 3A网络
3年前
Golang 常见设计模式之单例模式
之前我们已经看过了Golang常见设计模式中的装饰和选项模式,今天要看的是Golang设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。根据这一特性,我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go语言实现单例模式的方法有很多种,下面我们就一起来看一下。饿汉式饿汉式实现单例模式非
Wesley13 Wesley13
3年前
java 23种设计模式(五、单例模式)
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。单例模式的结构  单例模式的特点:单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。  饿汉式单例类publicclassEagerSingleton
Easter79 Easter79
3年前
Spring使用静态工厂方法创建Bean
1\.使用静态工厂方法创建Bean    使用静态工厂方法创建Bean实例时,class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类。因为Spring需要知道是用哪个工厂来创建Bean实例。另外,还需要使用factorymethod来指定静态工厂方法名,Spring将调用静态工厂方法(可能包含一组参数),
Stella981 Stella981
3年前
HttpClient使用总结
<divclass"htmledit\_views"<h1<aname"t0"</a一、使用方法</h1<p使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可。<br1.创建HttpClient对象。<br2.创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需
Wesley13 Wesley13
3年前
JAVA设计模式之单例设计模式
    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。  在JAVA中实现单例,必须了解JAVA内存机制,JAVA中实例对象存在于堆内存中,若要实现单例,必须满足两个条件:  1.限制类实例化对象。即只能产生一个对象。
Wesley13 Wesley13
3年前
Java多线程编程之单例模式
延迟加载:“懒汉模式”延迟加载是指在调用getInstance()方法时创建实例。常见的方法是在getInstance()方法中实例化new。实现代码如下:!(https://oscimg.oschina.net/oscnet/0b194956e9fd68db32050dd6439225bb86a.png)但是因为ge
Wesley13 Wesley13
3年前
Java单例模式
什么是单例模式  单例模式是在程序中,一个类保证只有一个实例,并提供统一的访问入口。为什么要用单例模式节省内存节省计算如对象实例中的一样的,那就不用每次都创建一个对象方便管理因为单例提供一个统一的访问入口,不需要创建N多个对象,很多工具类都用了单例实现,如日志、字符串工具类
Wesley13 Wesley13
3年前
C#单例
单例模式:步骤:1.定义静态私有对象2.构造函数私有化3.定义一个静态的,返回值为该类型的方法,一般以Getinstance/getInit为方法名称单例模式有懒汉和饿汉,最好使用饿汉1.饿汉式先实例化publicclassSingleton{privatestati
Stella981 Stella981
3年前
C#设计模式(1)——单例模式(Singleton)
单例模式即所谓的一个类只能有一个实例,也就是类只能在内部实例一次,然后提供这一实例,外部无法对此类实例化。单例模式的特点:1、只能有一个实例;2、只能自己创建自己的唯一实例;3、必须给所有其他的对象提供这一实例。普通单例模式(没有考虑线程安全)  ///<summary///单例模式
Stella981 Stella981
3年前
JVM02
文章目录前言对象创建1.类加载检查2.分配内存分配内存的方式内存分配的并发问题3.初始化零值4.设置对象头:5\.执行init方法;对象内存布局对象头实例数据对齐填充对象访问方式使用句柄
Wesley13 Wesley13
3年前
C++ 常用设计模式(学习笔记)
设计模式1、工厂模式在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。工厂模式作为一种创建模式,一般在创建复杂对象时,考虑使用;在创建简单对象时,建议直接new完成一个实例对象的创建。1.1、简单工厂模式主要特点是需要在工厂类中做判断,从而创造相应的产品,当