设计模式学习(一)——创建型模式之“单例模式”

常见的创建型模式:

  • 单例模式(手写)——只有一个实例
  • 工厂模式——由对象工厂生成对象
  • 建造者模式——组装复杂的实例
  • 原型模式——通过复制生成实例

学习设计模式方法:重构代码、看框架源码

设计模式六大原则:

  • 开闭原则:功能费对外开放,对修改单代码关闭
  • 里氏代换原则:能使用父类的地方一定可以使用子类替代,是对“开闭原则”的补充
  • 依赖倒转原则:是开闭原则的基础,表示系统功能扩展时可以使用具体类扩展
  • 接口隔离原则:使用多个隔离的接口比使用单个接口好,可降低依赖降低耦合
  • 最少知道原则:一个实体应该尽量少于其他实体之间发生相互作用,使得模块相对独立
  • 合成复用原则:尽量使用合成/聚合的方式,而不是使用继承

一、单例模式介绍

1.1、定义

保证一个类仅有一个实例,保证对象唯一性,提供一个访问它的全局访问点。

1.2、为什么要用单例模式

(1)单利模式有哪些应用场景:

有些对象实例只需要一个,并且只能有一个,

比如线程池、连接池、缓存、日志、任务管理器(只能打开一个)、枚举等对象,多了对象可能造成问题。

(2)单例模式的优点 / 为什么要用单例模式:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间;
  • 由于new操作次数减少,系统对内存的使用率也降低,从而减轻GC压力

(3)单例模式缺点

线程安全问题

(4)为什么不用全局变量(静态变量、实例变量)确保一个类只有一个实例,静态变量也可保证该类的实例只存在一个:

  • 程序要尽量避免全局变量的使用;
  • 如果这个对象耗资源,而且程序某次执行中一直没用,就会造成浪费资源。使用单例模式,需要时才创建对象,可避免不必要的浪费。

1.3、单例模式的实现

两种实现:

  • 饿汉式——全局的单例实例 在类装载时构建
  • 懒汉式——全局单例实例 在第一次使用时构建

一些规则:

  • 必须有一个private访问级别的构造函数,防止单例不会在系统中被其他代码内被实例化;
  • instance 成员变量和 uniqueInstance 方法必须是 static 的

二、单例模式代码示例

2.1、饿汉式(线程安全)

所谓“饿汉式”,指JVM在加载这个类时就马上创建此唯一的单例模式,不管用不用先创建好再说。不用时浪费空间用时节约时间,典型的空间换时间

 public class Singleton {
       //在静态初始化器中创建单例实例,这段代码保证了线程安全
        private static Singleton uniqueInstance = new Singleton();
        //Singleton类只有一个构造方法并且是被private修饰的,所以用户无法通过new方法创建该对象实例
        private Singleton(){}
        public static Singleton getInstance(){
            return uniqueInstance;
        }
    }

饿汉式(枚举方法——推荐):

更简洁,自动支持序列化机制,绝对防止多次实例化

public enum Singleton {
	 //定义一个枚举的元素,它就是 Singleton 的一个实例
    INSTANCE;     
    public void doSomeThing() {  
	     System.out.println("枚举方法实现单例");
    }  
}
public class ESTest {
	public static void main(String[] args) {
		Singleton singleton = Singleton.INSTANCE;
		singleton.doSomeThing();//output:枚举方法实现单例
	}
}

2.2、懒汉式(非线程安全)

所谓“懒汉式”指单例实例在第一次被使用时构建,而不是JVM在加载这个类时就创建

非线程安全版本(默认)

public class Singleton {  
      private static Singleton uniqueInstance;  
      private Singleton (){
      }   
      //没有加入synchronized关键字的版本是线程不安全的
      public static Singleton getInstance() {
          //判断当前单例是否已经存在,若存在则返回,不存在则再建立单例
	      if (uniqueInstance == null) {  
	          uniqueInstance = new Singleton();  
	      }  
	      return uniqueInstance;  
      }  
 }

线程安全版本:

public static synchronized Singleton getInstance() {  //加了synchronized 
	      if (instance == null) {  
	          uniqueInstance = new Singleton();  
	      }  
	      return uniqueInstance;  
      }  

2.3、双重检查加锁版本(线程安全):

(因为JVM本质重排序,可能会初始化多次,不推荐使用)

在程序中每次使用getInstance() 都要经过synchronized加锁这一层,增加时间开销而且可能阻塞,“双重检查加锁版本”可 解决这个问题。

首先检查是否实例已经创建,如果尚未创建,才进行同步。这样一来只有一次同步,相比于使用synchronized关键字的方法,可以大大减少getInstance() 的时间消费:

public class Singleton {
    //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
    private volatile static Singleton uniqueInstance;
    private Singleton() {
    }
    public static Singleton getInstance() {
       //检查实例,如果不存在,就进入同步代码块
        if (uniqueInstance == null) {
            //只有第一次才彻底执行这里的代码
            synchronized(Singleton.class) {
               //进入同步代码块后,再检查一次,如果仍是null,才创建实例
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

使用volatile 是为了防止指令重排序

详情可看:https://blog.csdn.net/RuiKe1400360107/article/details/103082629

 

2.4、静态内部类方式(线程安全):

只有通过显式调用getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}  

2.5、枚举方式(线程安全)

使用枚举实现单例模式优点:

实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障,避免通过反射和反序列化的漏洞, 缺点没有延迟加载。一般用于项目中定义常量

枚举方式实现单例: 

public class User {
	public static User getInstance() {
		return SingletonDemo04.INSTANCE.getInstance();
	}
    //枚举本身就是单例的,只会创建一次
	private static enum SingletonDemo04 {
		INSTANCE;
		// 枚举元素为单例
		private User user;
		private SingletonDemo04() {
			System.out.println("SingletonDemo04");
			user = new User();
		}
		public User getInstance() {
			return user;
		}
	}

	public static void main(String[] args) {
		User u1 = User.getInstance();
		User u2 = User.getInstance();
		System.out.println(u1 == u2);
	}
}

 

你可能感兴趣的:(设计模式)