java单例模式详解及其应用场景

1. 概述

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

很显然,单例模式是一种创建型模式。

2. 优缺点

  优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统运行效率。
  缺点:因为系统中只有一个实例,导致了单例类的职责过重,违背了“单一职责原则”,同时不利于扩展。

3. 单例模式实现方式

常见的单例模式实现方式有五种:饿汉式、懒汉式、双重检测锁、静态内部类和枚举单例。

3.1 饿汉式

public class SingletonDemo{
    //私有实例,类初始化即加载  
    private static SingletonDemo instance = new SingletonDemo();  
    //构造方法私有化
    private SingletonDemo(){}
    //类的唯一出口
    public static SingletonDemo  getInstance(){
       return instance;      
    } 
}
View Code

 1. 类加载时就初始化,浪费内存,不能延迟加载;
 2. 基于 classloader 机制避免了多线程的同步问题,线程安全;
 3. 没有加锁,调用效率高。

3.2 懒汉式

//版本1
public class SingletonDemo{
    //定义私有对象为null,先不实例
    private static SingletonDemo instance = null;
    //构造方法私有化
    private SingletonDemo(){}
    //在对外暴露的方法中实例化对象   
    public static SingletonDemo getInstance(){
        if instance == null{
            instance = new SingletonDemo()
        }
        return instance;
    }
}
//可以延迟加载,但是线程不安全

//版本2
public class SingletonDemo{
    //定义私有对象为null,先不实例
    private static SingletonDemo instance = null;
    //构造方法私有化
    private SingletonDemo(){}
    //在对外暴露的方法中实例化对象   
    public synchronized static SingletonDemo getInstance(){
        if instance == null{
            instance = new SingletonDemo()
        }
        return instance;
    }
}
//可以延迟加载,并且线程安全,但是效率不高

//版本3
public class SingletonDemo{
    //volatile修饰,禁止指令重拍,为了后续线程安全
    private volatile static SingletonDemo instance = null;
    //构造方法私有化
    private SingletonDemo(){}
    //在对外暴露的方法中实例化对象   
    public static SingletonDemo getInstance(){
        if instance == null{
            synchronized SingletonDemo.class{
                if instance == null{
                    instance = new SingletonDemo()
                }
            }          
        }
        return instance;
    }
}
//延迟加载,线程安全,效率较高        
View Code

重点说下版本3:双重锁检查,我们很容易理解。但是既然已经加锁的情况下,为什么instance还要用 volatile修饰呢?

首先,我们来看new一个对象的过程:https://www.cnblogs.com/daxiaq/articles/16662379.html

大致流程:(1)分配内存。(2)初始化实例。(3)返回地址。

由此可知new一个对象并非原子性的。

其次,我们都知道volatile的作用:防止指令重排

最后我们再来看看可能出问题的地方:new一个对象过程中,如果不用volatile修饰,有可能执行顺序为(1)(3)(2)。

那么可能拿到一个尚未初始化完成的对象,肯定会报错。

3.4 静态内部类

public class SingletonDemo{
    //静态内部类,调用时才会加载
    private static class InnerClass{
        private final static SingletonDemo INSTANCE = new  SingletonDemo()
    }
    //构造方法私有化
    private SingletonDemo(){}
    //直接获取实例
    public SingletonDemo getInstance(){
        return InnerClass.INSTANCE
    }
}    
View Code

 1. 利用了classloader机制来保证初始化 instance 时只有一个线程,线程安全;
 2. 只有通过显式调用 getInstance 方法时,才会显式装载静态内部类,从而实例化instance,延迟加载。

3.5 枚举

public enum SingletonDemo{
    INSTANCE;
}
View Code

枚举:天生就是单例最佳。但是实际工作使用中,很少使用。

1.简洁,无线程安全问题

2.自动支持序列化机制,自动防止反序列化重新创建新的对象,绝对防止多次实例化。

3.不是延迟加载的。

4 使用场景

1.饿汉式,正常工作中我们基本都是用这个,兼顾高效率,线程安全。譬如:封装提供一个统一的中间件对象(redis,mq等等)。延迟加载,防止反序列化创建多实例对于这些来说无需考虑。

2.懒汉式,基本上没见使用,正常工作中,尽量少使用锁,如果需要延迟加载的话也可以使用。

3.静态内部类,基本上没见使用,如果需要延迟加载,定义的单例可能用也可能不用,那么可以用这个。

4.枚举,基本上没见使用,如果这个单例需要防止反序列化创建多个实例,则可以使用这个。

 

posted @ 2022-09-07 16:27  蜗壳吃虾米  阅读(2725)  评论(0)    收藏  举报