龙空技术网

单例模式,看起来简单也有学问

技匠志之架构 205

前言:

现时同学们对“bigdecimal非空”大致比较关注,大家都需要学习一些“bigdecimal非空”的相关知识。那么小编也在网上搜集了一些有关“bigdecimal非空””的相关内容,希望我们能喜欢,兄弟们一起来了解一下吧!

单例是一个非常简单的创建型设计模式,我们今天来讨论一下他的原理和相关表现形式。

定义:创建对象只允许有一个实例自行的实例化而向整个系统提供这个实例。这种也好理解,譬如一个国家只有一个主席,唯一一个实例是全局的访问点。

实现思路(方法)

1.构造方法私有,无法使其实例化;

2.创建一个私有静态实例,获取为一个可用的实例;

3.类暴露一个公共方法,代表该单例类的实际应用。

我们可以通过一个类图来解释单例类:

示例代码:

public class SingleObject {   //创建 SingleObject 的一个对象,不管怎样我都能加载   private static SingleObject instance = new SingleObject();   //让构造函数为 private,这样该类就不会被实例化   private SingleObject(){}   //获取唯一可用的对象   public static SingleObject getInstance(){      return instance;   }   public void showMessage(){      System.out.println("Hello,我是一个单例类!");   }}

适用场景

1.要求生成唯一的序列号环境;

2. WEB系统共享一个访问点或者共享一个变量,如计数器;

3.数据库的驱动连接或者IO,需要消耗资源的情况;

4.需要大量定义静态常量或者方法,(比如金额计算工具,使用bigdecimal做金额处理);

优缺点分析

它的优点主要集中在使用场景上,在系统需要大量创建销毁对象的情况,大量使用或者依赖外围资源,多重占用资源情况比如大量使用缓存的首页系统,写文件操作场景使用单例模式就特别优势明显。

它的缺点也很突出。无法实现接口,没有接口的实现对功能的扩展显得局限。只关乎内部逻辑,无法拓展显得单一;也不利于测试特别是在并发环境下。

注意问题

1.单例情况下特别要注意线程同步问题,如下单例懒汉的表现形式,对于解决高并发问题是不利,我们需要改造单例模式,比如加锁、非空判断、枚举单例实现,下面我们有具体的实现;

2.单例不能实现cloneable接口,在使用单例的时候,私有了构造方法,但对象仍然可以被复制,违背了单一设计的原则。

表现形式

1、静态加载型

也叫饿汉型;不管是否调用都会创建对象,这样在大量访问大对象的时候就特别消耗资源,代码如下:

public class Singleton {// 声明私有构造方法private Singleton() {    }    private static Singleton instance = new Singleton();    // 获取实例(单例对象)    private static Singleton getInstance() {        return instance;    }       public void test() {        System.out.println("Hi,单例");    }}

2、懒汉型

当每次需要使用实例时,再去创建获取实例,而不是在类加载时就将实例创建好。

public class Singleton {    // 声明私有对象    private static Singleton instance;    // 获取实例(单例对象)    public static Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }    private Singleton() {    }    // 方法    public void sayHi() {        System.out.println("Hi,单例.");    }}

我们看到当它是在调用的时候才实例化对象,不会造成资源的浪费。而缺点也比较明显,那就是在多线程环境下是非线程是安全的,比如多个线程同时执行到 if 判断出,此时判断结果都是未被初始化,那么这些线程就会同时创建 n 个实例,这样就违背了单例的世界初衷。

我们怎么能够规避这样的问题呢?保证懒汉单例线程安全比较好的做法就是获取实例的方法上加上 synchronized(同步锁)修饰同时为了改进程序的执行效率,我们将 synchronized 放入到方法中,以此来减少被同步锁所修饰的代码范围实现代码如下:

public class Singleton {     private volatile static Singleton instance;// 声明私有对象    // 获取实例(单例对象)    public static Singleton getInstance() {        // 第一次判断        if (instance == null) {            synchronized (Singleton.class) {                // 第二次判断                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }    private Singleton() {    }    public void test() {        System.out.println("Hi,单例.");    }}

这就是双重检查锁型的单例,使用volatile修饰类对象,通过两次判空创建对象,保证线程安全的同时,内存持久刷新解决CPU 指令重排问题,从而完美地运行懒汉模式。

3、枚举型单例

枚举类型天然的线程安全而只需要装载一次,在序列化、反序列化、反射和克隆都不会再重新创建新对象所以用枚举实现单例也是比较好的实现单例方式。

public class Singleton {    // 枚举类型是线程安全的,并且只会装载一次    private enum SingletonEnum {        INSTANCE;        // 声明单例对象        private final Singleton instance;        SingletonEnum() {            instance = new Singleton();        }        private Singleton getInstance() {            return instance;        }    }    // 获取实例(单例对象)    public static Singleton getInstance() {        return SingletonEnum.INSTANCE.getInstance();    }    private Singleton() {    }    public void test() {       System.out.println("Hi,单例.")    }}

单例在框架里面的应用

1、Spring 中的 bean默认为单例,方便管理这些实例的生命周期;

2、在使用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; }  }}

3.也有一些组合设计模式,譬如单例和工厂,这些在实际框架中比较常见!

文章若有帮助欢迎点赞关注,也欢迎炸评指正!

标签: #bigdecimal非空