Java:单例模式详解

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点

单例模式-思维导图

解决的问题

一个全局使用的类频繁地创建与销毁。当您想控制实例数目,节省系统资源的时候,使用单例

核心要点

  • 1、单例类只能有一个实例。(静态变量)
  • 2、单例类必须自己创建自己的唯一实例。(私有化构造函数)
  • 3、单例类必须给所有其他对象提供这一实例。(全局访问点)

类图

饿汉、懒汉、枚举式、容器式单例

饿汉:类加载时就创建实例,这种被称为饿汉模式
懒汉:第一次被引用是,创建实例,被称为懒汉模式
枚举式单例:使用枚举的方式实现单例
容器式单例:将多个对象放在一个HashMap中,使用时直接从map中获取使用,而不单独创建,优点是可以创建多种单例对象

代码实现

饿汉模式-静态常量

  1. 线程安全,可能造成内存浪费
  2. 实现方式简单,类加载是就实例化 了。
/**
 * 饿汉模式-静态常量
 *
 * 避免了线程同步的问题
 * 造成内存的浪费
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 9:36
 */
public class Singleton01 {
    
    

    public static void main(String[] args) {
    
    
        Singleton singleton = Singleton.getSingleton();
        Singleton singleton1 = Singleton.getSingleton();
        System.out.println(singleton.hashCode());
        System.out.println(singleton1.hashCode());
    }


}

class  Singleton{
    
    

    //私有化构造函数
    private Singleton() {
    
    
    }

    //唯一实例
    private final static Singleton SINGLETON=new Singleton();

    //全局访问点
    public static Singleton getSingleton(){
    
    
        return SINGLETON;
    }

}

饿汉模式-静态代码块

  1. 线程安全,可能造成内存浪费
  2. 实现方式简单
/**
 * 饿汉模式-静态代码块
 *
 * 避免了线程同步的问题
 * 造成内存的浪费
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 9:36
 */
public class Singleton02 {
    
    

    public static void main(String[] args) {
    
    

    }


}

class  Singleton{
    
    

    //私有化构造函数
    private Singleton() {
    
    
    }

    static {
    
    
        SINGLETON=new Singleton();
    }
    //唯一实例
    private  static Singleton SINGLETON;


    //全局访问点
    public static Singleton getSingleton(){
    
    
        return SINGLETON;
    }

}

懒汉-线程不安全

  1. 线程不安全
  2. 起到了懒加载的效果
/**
 * 懒汉,线程不安全
 * 起到了懒加载的效果
 * 多线程不能使用
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 9:49
 */
public class Singleton03 {
    
    

    public static void main(String[] args) {
    
    

    }

}

class  Singleton{
    
    

    //私有化构造函数
    private Singleton() {
    
    
    }


    //唯一实例
    private  static Singleton SINGLETON;

    //全局访问点
    //懒汉,判断是否为null,是:new 否:返回实例
    public static Singleton getSingleton(){
    
    
        //多线程进入会出问题。
        if (SINGLETON==null){
    
    
            SINGLETON=new Singleton();
        }
        return SINGLETON;
    }

}

懒汉-线程安全(同步代码块)

  1. 解决了线程安全问题
  2. 效率低,不推荐使用
/**
 * 懒汉,线程安全-同步方法
 * 效率太低,不建议使用
 * 使用synchronized加锁
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 9:55
 */
public class Singleton04 {
    
    

    public static void main(String[] args) {
    
    
        
    }
}
class  Singleton{
    
    

    //私有化构造函数
    private Singleton() {
    
    
    }


    //唯一实例
    private  static Singleton SINGLETON;

    //全局访问点
    //懒汉,判断是否为null,是:new 否:返回实例
    //使用synchronized加锁,多线程访问,每次都会阻塞、上锁
    public static synchronized Singleton getSingleton(){
    
    

        if (SINGLETON==null){
    
    
            SINGLETON=new Singleton();
        }
        return SINGLETON;
    }

}

懒汉-双检锁

  1. 面试常问,双检锁的形式。if -sycn-if-new
  2. 线程安全+延迟加载+效率高
  3. 使用了volatile修饰变量:线程间可见
/**
 * 懒汉-双检锁:推荐使用
 * 懒汉+线程安全+volatile
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 10:34
 */
public class Singleton05  {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
    
    
            pool.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(Singleton.getSingleton().hashCode());
                }
            });
        }
        pool.shutdown();
    }
}

class  Singleton {
    
    

    //私有化构造函数
    private Singleton() {
    
    
    }

    //唯一实例,使用volatile修饰:线程间可见
    private  static volatile Singleton SINGLETON;

    //全局访问点
    //懒汉、双检锁
    //线程安全+懒加载
    public static Singleton getSingleton(){
    
    

        if (SINGLETON==null){
    
    
            synchronized (Singleton.class){
    
    
                if (SINGLETON==null){
    
    
                    SINGLETON=new Singleton();
                }
            }
        }
        return SINGLETON;
    }

}

懒汉-静态内部类

  1. 使用类装载的机制来保证初始化实例时只有一个线程访问。
  2. 静态内部类方式在外部类装载时,不会立即实例化。只有调用getSingleton()方法时,才会装载SingletonInstance类,从而实例化Singleton
/**
 * 懒加载-静态内部类:推荐使用
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 10:49
 */
public class Singleton06 {
    
    

    public static void main(String[] args) {
    
    

        ExecutorService pool = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 20; i++) {
    
    
            pool.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(Singleton.getSingleton().hashCode());
                }
            });
        }

        pool.shutdown();

    }

}

//静态内部类-推荐使用
class  Singleton{
    
    

    //私有化构造函数
    private Singleton() {
    
    
    }

   
    //外部类加载时,内部类不会加载
    //只有内部类使用时,才会被加载,懒加载
    //JVM加载类是线程安全的
    private static class SingletonInstance{
    
    
        //唯一实例
        private  static final Singleton SINGLETON=new Singleton();
    }

    //全局访问点
    public static Singleton getSingleton(){
    
    

        return SingletonInstance.SINGLETON;
    }

}

枚举式单例

/**
 * 使用枚举
 * 使用枚举的方式,不仅能够避免多线程同步问题,还能防止反序列化重新创建对象
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 10:59
 */
public class Singleton07 {
    
    


    public static void main(String[] args) {
    
    
        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 = Singleton.INSTANCE;
        System.out.println(instance.hashCode());
        System.out.println(instance1.hashCode());

        instance.sayHello();

        //Runtime
    }

}

//使用枚举的方式,不仅能够避免多线程同步问题,还能防止反序列化重新创建对象
//推荐使用
enum Singleton{
    
    

    INSTANCE;

    public void sayHello(){
    
    
        System.out.println("Hello~~~");
    }

}

容器式单例

  1. 实现方式类似SpringIOC
  2. IOC容器的工作:负责创建、管理类实例,向使用者提供实例。
  3. IOC容器负责来创建类实例对象,使用者需要实例就从IOC容器中get。
/**
 * 模仿IOC容器,将创建的对象放在Map集合中
 * 加载该类时会读取配置文件中,将类放入Map中
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/5/19 - 8:45
 */
public class BeanFactory {
    
    

    //定义一个properties对象
    private static Properties props;

    //定义一个Map,用于存放我们创建的对象(单例,当类加载之后就有了对象,之后从Map中获取)
    private static Map<String,Object> beans = new ConcurrentHashMap<String,Object>();

    //容器
    static {
    
    
        try {
    
    
            props=new Properties();
            //将bean.properties放在了resources路径下
            InputStream is=BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(is);
            //实例化容器

            //从配置文件中获取所有key值
            Enumeration<Object> keys = props.keys();
            while (keys.hasMoreElements()){
    
    
                //取出每一个key
                String key = keys.nextElement().toString();
                //根据key获取value
                String path = props.getProperty(key);
                Object value=Class.forName(path).newInstance();
                //放入容器中
                beans.put(key,value);
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }

    //提供一个访问Map容器的入口
    //全局访问点
    public static Object  getInstance(String name){
    
    
        return beans.get(name);
    }

}

拓展

JDK源码-Runtime

使用饿汉模式

public class Runtime {
    
    
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
    
    
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {
    
    }
    
}

Spring-IOC容器单例

源码类:DefaultSingletonBeanRegistry
方法:registerSingleton

//一级缓存:存放完整对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//三级缓存:存储创建单例BEAN的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

//二级缓存:存放未属性注入的对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);


//注册单例Bean
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
    
    
    Assert.notNull(beanName, "Bean name must not be null");
    Assert.notNull(singletonObject, "Singleton object must not be null");
    //锁住一级缓存	
    synchronized (this.singletonObjects) {
    
    
        Object oldObject = this.singletonObjects.get(beanName);
        //当对象存在时,报错
        if (oldObject != null) {
    
    
            throw new IllegalStateException("Could not register object [" + singletonObject +
                                            "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
        }
        //当对象不存在,添加到容器中
        addSingleton(beanName, singletonObject);
    }
}

//添加单例的方法,通过加锁将我们的单例放到map中,然后通过getSingleton获取
protected void addSingleton(String beanName, Object singletonObject) {
    
    
    synchronized (this.singletonObjects) {
    
    
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

解决-反射破坏单例

/**
 * 反射破坏单例
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 12:20
 */
public class Singleton08 {
    
    

    public static void main(String[] args) {
    
    
        try {
    
    
            //正常获取对象
            Singleton s1 = Singleton.getSingleton();
            System.out.println(s1.hashCode());

            //反射获取对象
            Class<Singleton> singletonClass = Singleton.class;
            Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor(null);
            constructor.setAccessible(true);
            Singleton s2 = constructor.newInstance();
            System.out.println(s2.hashCode());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

    }

}

class  Singleton {
    
    

    //私有化构造函数
    private Singleton() {
    
    

        if (Singleton.SINGLETON!=null){
    
    
            throw new RuntimeException("不允许创建多个实例");
        }

    }

    //唯一实例
    private  static volatile Singleton SINGLETON;

    //全局访问点
    //懒汉、双检锁
    //线程安全+懒加载
    public static Singleton getSingleton(){
    
    

        if (SINGLETON==null){
    
    
            synchronized (Singleton.class){
    
    
                if (SINGLETON==null){
    
    
                    SINGLETON=new Singleton();
                }
            }
        }
        return SINGLETON;
    }

}

反射破坏单例

解决-序列化破坏单例

使用readResolve()方法或者使用枚举式单例、容器式单例

/**
 * 序列化破坏单例
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2022/9/2 - 12:30
 */
public class Singleton09 {
    
    
}

class  Singleton implements Serializable {
    
    

    //私有化构造函数
    private Singleton() {
    
    
    }

    //唯一实例
    private  static  Singleton SINGLETON=new Singleton();

    //饿汉
    public static Singleton getSingleton(){
    
    

        return Singleton.SINGLETON;
    }

    //重点方法:解决序列化破坏单例
    //写一个这个方法就解决了
   
    private Object readResolve(){
    
    
        return Singleton.SINGLETON;
    }

}

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏

猜你喜欢

转载自blog.csdn.net/promsing/article/details/126663490