设计模式之单例模式

栏目: 后端 · 发布时间: 7年前

内容简介:单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例对于系统的某些类来说,只含有一个实例很重要,有助于我们协调系统的整体行为。比如借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。

基本定义

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例

对于系统的某些类来说,只含有一个实例很重要,有助于我们协调系统的整体行为。比如 线程池、缓存、日志对象 等等都会被设计为单例,设计的初衷也是为了避免不一致状态。

2.常见的单例模式

饿汉式单例

私有的默认构造函数
线程安全
类加载就完成实例化
public class Singleton {
    private Singleton(){}
    private final static Singleton SINGLETON = new Singleton();
    public static Singleton getInstance(){
        return SINGLETON;
    }
}
复制代码

懒汉式单例

  1. 线程不安全,单线程环境下可以使用。
  2. 延迟加载对象。
  3. 多线程环境下可能会产生多个Singleton对象
public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
复制代码

懒汉式单例(同步方法解决线程安全问题)

  1. synchronized同步方法,此方式线程安全。
  2. 效率十分低下。每个线程获取实例,都需要进行等待方法锁释放。
public class Singleton {
      private static Singleton singleton;
      private Singleton() {}
      public static synchronized Singleton getInstance() {
          if (singleton == null) {
              singleton = new Singleton();
          }
          return singleton;
      }
  }
复制代码

懒汉式单例(同步实例-线程不安全写法)

  1. 原本觉得同步整个方法的效率会比较低,假设创建实例中还存在其他的一些操作。所以改成同步创建实例那一步。
  2. 在if(singleton == null)这一步仍然会产生线程安全问题。
private static Singleton singleton;
      private Singleton() {}
      public static Singleton getInstance() {
          if (singleton == null) {
              synchronized (Singleton.class) {
                  singleton = new Singleton();
              }
          }
          return singleton;
      }
复制代码

懒汉式单例(双重检查----推荐使用)

  1. 上面的同步创建实例的思想是OK的,但是写法上却走向了线程不安全。
  2. 通过两次if(singleton == null)的比较,保证线程安全,且同步实例化代码只需要执行一次,后面再次访问,都不会再进入同步代码块,直接到达下面的return。
  3. 此写法 线程安全 ,且 懒加载 .
public class Singleton {
    /*volatile 关键字确保当uniqueInstance变量被初始化成Singleton实例时,
    多个线程正确地处理uniqueInstance
    */
      private static volatile Singleton singleton;
      private Singleton() {}
      public static Singleton getInstance() {
          if (singleton == null) {
              synchronized (Singleton.class) {
                  if (singleton == null) {
                      singleton = new Singleton();
                  }
              }
          }
          return singleton;
      }
  }
复制代码

懒加载单例(静态内部类实现----推荐使用)

  1. 线程安全,懒加载,效率高。
  2. 这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
public class Singleton {
    private  static  boolean initialized =false;
    /*
    要想使用内部类,必然需要先初始化内部类,
    如果不使用,则不会加载内部类。
    * */
    //构造方法这一段是假如反射攻击的时候能抛出异常。
    private Singleton(){
        synchronized (Singleton.class){
            if (initialized==false){
                initialized=initialized;
            }else{
                throw new RuntimeException("单例已经被侵犯");
            }
        }
    }
    //当调用这个方法的时候将会加载内部类
    //static 为了使得单例空间可以共享
    //final  保证这个方法不会被重写重载
    public  static  Singleton getInstance(){
        //在访问这个结果以前,一定会先加载下面的内部类
        return LazyHolder.LAZY_THREE;
    }
    //默认不加载
    private static class LazyHolder{
        private static  final LazyThree LAZY_THREE =new Singleton();
    }
}
复制代码

懒加载单例(枚举方式----推荐使用)

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。

public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}
复制代码

3.spring中单例模式的实现:注册式

//Spring 中的单例实现  注册式实现
public class BeanFactory {
	/*
	注册式单例维护的实际是一组单例类,若已经注册过,那么从IOC容器中直接返回,
	若没有注册过,先注册,再返回。
	*/
    public static Map<String,Object> ioc=new ConcurrentHashMap<>();

    public  static  Object getBean(String className){
        if(ioc.containsKey(className)){
            return  ioc.get(className);
        }else{
            Class clazz=null;
            Object obj= null;
            try {
                obj = Class.forName(className).newInstance();
                return ioc.put(className,obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        }
    }
}
复制代码

4.解决反序列化重新创建对象的问题

public class Seriable implements Serializable {
    public  final  static  Seriable  INSTANCE =new Seriable();
    private  Seriable(){}
    public static  Seriable getInstance(){
        return  INSTANCE;
    }
    /*在反序列化的时候会重新创建对象 (new )
    * 但是有的对象是单例的呢,你不能创建多个对象吧
    * 所以通过下面这个方式可以控制只生成一个对象。
    * */
    private  Object readResolve(){
        return INSTANCE;
    }
}
复制代码

5.单例模式为什么必须是静态的

先搞清单例模式的实现过程:

  1. 先将该类的构造函数私有化(目的是禁止其他程序创建该类的对象);
  2. 其次,在本类中自定义一个对象(既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例);
  3. 最后,提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式);

核心就是你只能用我自己创建的对象。

程序调用类的方式:

  1. 创建类的一个对象,用该对象去调用类中方法
  2. 使用类名直接调用类中方法,格式“类名.方法名()”

上面说了,构造函数私有化后第一种情况就不能用,只能使用第二种方法。 而使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的。这就是单例模式为什么必须是静态的原因。

谢谢以下前辈:

参考: www.cnblogs.com/zhaoyan001/…

参考: zhidao.baidu.com/question/22…


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Is Parallel Programming Hard, And, If So, What Can You Do About

Is Parallel Programming Hard, And, If So, What Can You Do About

Paul E. McKenney

The purpose of this book is to help you understand how to program shared-memory parallel machines without risking your sanity.1 By describing the algorithms and designs that have worked well in the pa......一起来看看 《Is Parallel Programming Hard, And, If So, What Can You Do About 》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具