设计模式之美--单例模式

单例模式的几个实现方式:实现递增id生成器

  1. 饿汉式
/**
 * 饿汉式(不支持延迟加载)
 * @author lq
 * @version : IdGenerator.java, v 0.1 2022年12月13日 10:19 lq Exp $
 */
public class IdGenerator {
    //long类型静态变量和成员变量默认值为0
    private AtomicLong id = new AtomicLong();
    private static final IdGenerator instance = new IdGenerator();

    private IdGenerator(){
    }

    public static IdGenerator getInstance() {
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}
  1. 懒汉式
/**
 * 懒汉式(延迟加载,实例真正使用的时候才去初始化)
 * 如果实例加载耗费资源比较多,当资源不够时会报错。
 * 理解:按照fail-fast原则,如果有问题应该尽早暴露出来去解决。
 * 所以在项目启动时,就应该加载这些资源,如果资源不够,触发报警机制去解决问题
 *
 * 缺点:因为加了同步锁 synchronized 所以如果这种工具使用程度高的话,是有性能瓶颈的。
 * @author lq
 * @version : IdGeneratorLazy.java, v 0.1 2022年12月13日 10:30 lq Exp $
 */
public class IdGeneratorLazy {

    private AtomicLong id = new AtomicLong();
    private static IdGeneratorLazy instance;

    private IdGeneratorLazy() {
    }

    public static synchronized IdGeneratorLazy getInstance() {
        if (instance == null) {
            instance = new IdGeneratorLazy();
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}
  1. 双重检测
/**
 * 双重检测(支持延迟加载,也支持高并发)
 * 存在指令重排序问题,instance = new IdGeneratorDoubleLock();
 * 可以给instance 加上 volatile 关键字禁止指令重排序
 * @author lq
 * @version : IdGeneratorDoubleLock.java, v 0.1 2022年12月13日 10:43 lq Exp $
 */
public class IdGeneratorDoubleLock {

    private AtomicLong id = new AtomicLong();

    private static volatile IdGeneratorDoubleLock instance;

    private IdGeneratorDoubleLock() {
    }

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

    public long getId() {
        return id.incrementAndGet();
    }
}
  1. 静态内部类
/**
 * 静态内部类(类似饿汉式,但支持延迟加载)
 * 当 IdGeneratorStaticInner 被加载时,内部类不会被加载,只有当getInstance()被调用时才会被加载;
 * @author lq
 * @version : IdGeneratorStaticInner.java, v 0.1 2022年12月13日 10:54 lq Exp $
 */
public class IdGeneratorStaticInner {
    private AtomicLong id = new AtomicLong();
    private IdGeneratorStaticInner() {
    }

    private static class SingletonHolder {
        private static final IdGeneratorStaticInner instance = new IdGeneratorStaticInner();
    }

    public static IdGeneratorStaticInner getInstance() {
        return SingletonHolder.instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}
  1. 枚举
/**
 * 枚举
 * @author lq
 * @version : IdGeneratorEnum.java, v 0.1 2022年12月13日 11:10 lq Exp $
 */
public enum  IdGeneratorEnum {
    INSTANCE;
    private AtomicLong id = new AtomicLong();
    public long getId() {
        return id.incrementAndGet();
    }
}

单例模式的应用举例:日志工具类

/**
 * @author lq
 * @version : Logger.java, v 0.1 2022年12月13日 11:12 lq Exp $
 */
public class Logger {

    private FileWriter writer;
    private static final Logger instance = new Logger();

    private Logger() {
        File file = new File("/log.txt");
        try {
            writer = new FileWriter(file, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Logger getInstance() {
        return instance;
    }
    public void log(String message) throws IOException {
        writer.write(message);
    }
}

单例模式的弊端

  1. 隐藏类之间的依赖关系:单例模式不需要显示创建,不需要依赖参数传递,直接调用就行。依赖关系隐蔽。
  2. 影响代码的扩展性:出现需要创建两个或多个实例的情况;
    举例:数据库连接池。
    • 初期设计,认为项目系统中只应存在一个数据库连接池;
    • 性能问题:运行时间长的sql占用系统资源,导致其他sql请求无法响应。设计成两个连接池,一个支持运行速度慢的,一个支持常规sql。
    • 故,数据库连接池、线程池都没有去设计成单例类的形式。
  3. 影响代码的可测试性:静态变量相当于全局变量。需要考虑被所有单元测试类消费变更的问题。(在同一个jre中,静态变量是共享的。)
  4. 不支持包含参数的构造函数
    支持的解决思路:
    • 通过init()同步静态方法去初始化参数,在使用getInstance()前调用init方法;
    • 将参数放到getInstance方法中,缺点:只有第一次参数是生效的。后面的使用参数都是无效的,使用到的都是第一次初始化时的入参结果。
    • 将参数放到全局变量中。正常使用单例类。

单例模式的唯一性

单例模式创建的对象是进程唯一的。进程内唯一,进程间不唯一;线程内,线程间都唯一。

线程唯一的单例实现

/**
 * 线程唯一单例
 * @author lq
 * @version : IdGeneratorThread.java, v 0.1 2022年12月13日 10:54 lq Exp $
 */
public class IdGeneratorThread {
	private AtomicLong id = new AtomicLong();
	private static final ConcurrentHashMap<Long, IdGeneratorThread> instances = new ConcurrentHashMap();
	private IdGeneratorThread(){}
	public static IdGeneratorThread getInstance(){
		Long currentThreadId = Thread.currentThread().getId();
		instances.putIfAbsent(currentThreadId, new IdGeneratorThread());
		return instances.get(currentThreadId);
	}
	public long getId(){
		return id.incrementAndGet();
	}
}

集群下的单例模式

即分布式单例模式,进程间唯一。
思考:为什么要设计成集群下的单例?有什么意义?
认为:如果要做到这样的效果,没有必要强行用单例来做。常规的开发思路没有什么问题。
举例:分布式递增id生成器:用数据库记录id当前值,每次获取都从数据库获取,用数据库的事务和锁来达到线程安全的作用;

多例模式

/**
 * 日志框架的雏形demo
 * @author lq
 * @version : LoggerNew.java, v 0.1 2022年12月13日 14:47 lq Exp $
 */
public class LoggerNew {
    private static final ConcurrentHashMap<String, LoggerNew> instances = new ConcurrentHashMap<>();

    private LoggerNew() {
    }

    public static LoggerNew getInstance(String className) {
        instances.putIfAbsent(className, new LoggerNew());
        return instances.get(className);
    }
    
    public void log(String message) {
        //打印日志
    }
}
posted @ 2022-12-13 11:33  lq-12040  阅读(52)  评论(0)    收藏  举报