剑指offer:2. 实现Singleton模式,考虑内存及并发环境

题目

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

分析

最简单实现

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

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

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

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

实现方式

  • 1. 单线程(懒汉式、饿汉式)
  • 2. 多线程工作效率不高(加锁获取实例的方法)
  • 3. 加同步锁前后两次判断实例是否已存在
  • 4. 利用静态初始化创建实例(推荐,线程安全,会占用一部分内存)
  • 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创建实例对象的方式有哪些?

参考

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

树木勺子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值