转载

聊一聊单例及框架中的单例

单例模式是设计模式中最简单也是最常用的设计模式之一,单例顾名思义就是系统中只有唯一实例,这个唯一实例的获取方式就是通过一个方法的调用获得,而不是通过正常流程中的new实例化。多年前在学习设计模式时就了解到单例有多种实现方式,今天就来总结一下,并且探索一下在当前java生态框架中的应用场景。

正文

先回顾一下单例的几种实现方式,懒汉式、饿汉式、枚举实现。

懒汉式

懒汉式指的是在服务启动时并不创建单例的实例,而是在需要时创建,并且为了保证线程安全,在创建时方法设置了同步锁。

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton(){}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
复制代码

还存在一种对上面的改进,为了提升效率,防止多个线程访问getSingleton时的,只是在创建单例实例时再去加同步锁。

public class LazySingletonV2 {
    private volatile static LazySingletonV2 singleton;
    private LazySingletonV2(){}
    public static LazySingletonV2 getSingleton() {
        if (singleton == null) {
            synchronized (LazySingletonV2.class) {
                if (singleton == null) {
                    singleton = new LazySingletonV2();
                }
            }
        }
        return singleton;
    }
}
复制代码

饿汉式

饿汉式则比较直接,在服务启动时就已经初始化好了单例实例,比较推荐。

public class HunSingleton {

    private static HunSingleton instance = new HunSingleton();

    private HunSingleton() {
    }

    public static HunSingleton getInstance() {
        return instance;
    }
}
复制代码

枚举类型

上述的一些方式都可能会被反射等方式破坏单例,因此又衍生出了枚举的单例,如下:

public class EnumSingleton {
    private EnumSingleton(){}
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }
    private static enum Singleton{
        INSTANCE;

        private EnumSingleton singleton;
        //JVM会保证此方法绝对只调用一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}
复制代码

应用场景

上述是几种单例的简单实现方式,那Java生态中也有很多框架用到了单例模式。如spring ioc的bean管理、log4j日志管理框架等。

spirng框架

对于最常用的spring框架来说,我们经常用spring来帮我们管理一些无状态的bean,其默认设置为单例,这样在整个spring框架的运行过程中,即使被多个线程访问和调用,这些“无状态”的bean就只会存在一个,为他们服务。那么“无状态”bean指的是什么呢?

无状态:当前我们托管给spring框架管理的javabean主要有service、mybatis的mapper、一些utils,这些bean中一般都是与当前线程会话状态无关的,没有自己的属性,只是在方法中会处理相应的逻辑,每个线程调用的都是自己的方法,在自己的方法栈中。

有状态:指的是每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean,因此在将一些bean如User这些托管给spring管理时,需要设置为prototype多例,因为比如user,每个线程会话进来时操作的user对象都不同,因此需要设置为多例。

但spring框架在实现对bean管理时,跟上述的懒汉、饿汉均不相同,是通过ConcurrentHashMap单例注册表的方式实现的,在这就不再多阐述。

单例bean的一些优势与劣势:

优势: 1.减少了新生成实例的消耗,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法; 2.减少jvm垃圾回收; 3.可以快速获取到bean;

劣势: 单例的bean一个最大的劣势就是要时刻注意线程安全的问题,因为一旦有线程间共享数据变很可能引发问题。

log4j

在使用log4j框架时也注意到了其使用的是单例,当然也为了保证单个线程对日志文件的读写时不出问题,与使用spring管理bean的目标不是相似,如下为其logfactory单例创建的源码:

class Log4jLoggerFactory {
    private static ConcurrentMap<String, Logger> log4jLoggers = new ConcurrentHashMap();

    Log4jLoggerFactory() {
    }

    public static Logger getLogger(String name) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            Logger newInstance = new Logger(name);
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }

    public static Logger getLogger(String name, LoggerFactory loggerFactory) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            Logger newInstance = loggerFactory.makeNewLoggerInstance(name);
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }
}
复制代码

既然log4j使用单例对单个文件读写,那么如何实现多线程环境下同时读写多个文件,这个博文实现了对每个线程独立的log文件的读写, blog.csdn.net/guan0005/ar…

结语

本文对单例模式进行了简单的总结,及目前生态中单例的实用场景,大家如果有好的实现方式或者更多的实用场景可以补充一下。

原文  https://juejin.im/post/5d6291036fb9a06acf2b665a
正文到此结束
Loading...