1.是什么?

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

    确保一个类只有一个实例——————使用private 这个实例只属于当前类—-实例是当前类的类成员变量,静态变量 构造方法为private修饰,并且拥有一个当前类的静态成员变量 提供实例 ————向外界提供实例

**

2.为什么要用单例模式?

主要解决:一个全局使用的类频繁地创建与销毁。

使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:**getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
如果在系统中不使用单例模式的话,在碰到多线程访问的时候,printer就会给要请求的类,分别在内存中new出一个printer对象,让这些请求的类去做print方法。这样大量占有内存,就会导致系统运行变慢。
单例模式,让多线程处于等待的状态,一个 一个的去解决,这样,即节约内存,提交了运行的成本。也就是单例存在的意义。

3.优缺点

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。

缺点:

有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
若实例的对象存在一个属性初始值为10,在某个地方去使用它修改值为20,则可以随意改动其属性值。
所有单例模式最佳实践就是无属性。

4.案例

image.png

1.饿汉式

在类进行加载的时候就进行实例化,不管你有没有用到,都先建好了再说。

  • 好处是没有线程安全的问题;
  • 坏处是浪费内存空间。

image.png

2.懒汉式

懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。
image.png

  1. /**
  2. * 实例化的时机:
  3. * 懒汉式: 在类加载的时候不进行实例化,在第一次使用的时候进行实例化
  4. * 只有在调用的时候,(即使用对象的时候)才会调用getInstance方法进行实例化
  5. */
  6. class Singleton{
  7. //唯一的实例 -- 类变量
  8. private static Singleton singleton;
  9. //私有的构造方法
  10. private Singleton(){}
  11. //这里进行同步处理 防止被多次实例化
  12. public synchronized static Singleton getInstance(){
  13. if (singleton == null)
  14. singleton = new Singleton();
  15. return singleton;
  16. }
  17. }

说明: 若A、B线程执行的过程中,同时调用getInstance``_()方法_,判断当前的singleton对象都为空。 之后A、B都会对其实例化,若不做同步处理就会被实例化两次。

3.双重检查锁

综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
image.png

由同步处理的懒汉式改进,之前同步处理的是整个方法,实例化的操作仅仅发生在第一次。之后再判断就不可能为空了,也就不会实例化对象了,但是依然会执行同步操作。 改进:在方法的内部添加同步块,来实现缩小同步减小范围。在第一次判断对象为空的时候,就加锁执行同步操作,在锁的内部再判断,若还为空就,实例化对象。第二次访问的时候,则不需执行同步代码块,直接将存在的对象返回即可。

注意:
使用双重检查锁进行实例化对象的时候必须使用Volatile关键字修饰。避免对象创建的时候重排序。

4.静态内部类

静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

  1. public class Singleton{
  2. private static class SingletonHolder{
  3. private static final Singleton INSTANCE = new Singleton();
  4. }
  5. private Singleton(){}
  6. public static final Singleton getInstance(){
  7. return SingletonHolder.INSTANCE;
  8. }
  9. }
  10. --------------------------------------
  11. public class SingletonPattern {
  12. public static void main(String[] args) {
  13. Singleton instance = Singleton.getInstance();
  14. System.out.println(instance);
  15. }
  16. }

总结 ,一般情况下,懒汉式(包含线程安全和线程不安全梁总方式)都比较少用;饿汉式和双检锁都可以使用,可根据具体情况自主选择;在要明确实现 lazy loading 效果时,可以考虑静态内部类的实现方式。

5.特殊情况

单例模式 确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式。
但是在下面两中种情况可以实例多个对象。
在分布式的项目中: 会存在多个java虚拟机,各个虚拟机都会有一个实例;
多个类加载器:一个虚拟机,使用了多个类加载器,同时加载这个类,产生多个实例。