Java 设计模式之单例模式(一)

《Java 设计模式之单例模式(一)》

一、背景

没有太多原由,纯粹是记录和总结自己从业以来经历和学习的点点滴滴。

本篇内容为 Java 设计模式系列的第一篇。

二、简单介绍

2.1 定义

单例模式是一种对象创建型模式,保证一个类只有一个实例,并且提供能对该实例加以访问的全局方法。

2.2 应用场景

1) 操作系统的任务管理器

2) 读取配置文件的类

3) 数据库连接池

4) Javaweb 中的 Servlet 实例

5) Spring 创建的实例,默认为单例

三、实现方式

常用的实现方式有饿汉式、懒汉式和枚举类。

本篇文章主要讲饿汉式和懒汉式的单例模式。

共同点:将构造方法私有化,并且提供一个公共的方法访问该类的实例对象。

我们以任务管理器为例进行演示。

3.1 饿汉式

public class TaskManager {
    private static TaskManager tm = new TaskManager();
    
    private TaskManager() {
        
    }
    
    public static TaskManager getInstance() {
        return tm;
    }
}

优点:线程安全,不用加同步锁,因此在高并发时调用效率高。

缺点:不能懒加载,如果不使用该类的实例,浪费内存资源。

3.2 懒汉式

public class TaskManager {
    
    private static TaskManager tm;
    
    private TaskManager() {
        
    }
    
    public static synchronized TaskManager getInstance() {
        if (tm == null) {
            tm = new TaskManager();
        }
        
        return tm;
    }
}

优点:实现懒加载,合理利用系统资源。

缺点:需要添加同步锁,高并发时调用效率不高。

注意点:上边的懒汉式可以通过反射机制创建多个实例。

public class Client {
    public static void main(String[] args) throws Exception {
        
        Class<?> clazz = Class.forName("com.light.gof.singleton.TaskManager");
        
        Constructor<?> constructor = clazz.getDeclaredConstructor(null);
        
        // 跳过检测机制
        constructor.setAccessible(true);
        
        TaskManager tm1 = (TaskManager) constructor.newInstance();
        
        TaskManager tm2 = (TaskManager) constructor.newInstance();
        
        System.out.println(tm1 == tm2);// 结果返回 false
    }
}

3.3 优化方式

将饿汉式和懒汉式的优点集中起来。

public class TaskManager {
    
    private TaskManager() {
        
    }
    
    private static class InnerTaskManager {
        private static final TaskManager tm = new TaskManager();
    }
    
    public static TaskManager getInstance() {
        return InnerTaskManager.tm;
    }
}

外部类没有静态属性,因此不会像饿汉式立即加载对象。

只有当调用公共方法(getInstance)时,才会加载静态内部类。加载内部类的过程是线程安全的。

内部类中通过 static final 确保内存中只有一个外部类的实例,因为实例变量(tm)只能被赋值一次。

四、UML 类图

类图表现如下:

《Java 设计模式之单例模式(一)》

五、性能比较

public class Client {
    public static void main(String[] args) throws Exception {
        // 线程数
        int num = 10;
        
        // 计数器
        CountDownLatch cd = new CountDownLatch(num);
        
        long t1 = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        // 此处替换不同实现方式的单例代码进行测试
                        TaskManager tm = TaskManager.getInstance();
                    }
                    cd.countDown();
                }
            }).start();
        }
        // 主线程等待
        cd.await();
        
        System.out.println("耗时:" + (System.currentTimeMillis() - t1) + "ms");
    }
}

测试结果:

实现方式耗时
饿汉式3ms
懒汉式12ms
内部类方式4ms

测试结果是相对的,硬件配置不同,测试结果不同,但是对于这个 3 种实现方式,它们的用时比例应该大致相同。

    原文作者:算法小白
    原文地址: https://juejin.im/entry/5a0127846fb9a044ff30ddea
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞