zoukankan      html  css  js  c++  java
  • Java 设计模式之单例模式(一)

    原文地址:Java 设计模式之单例模式(一)
    博客地址:http://www.extlight.com

    image

    一、背景

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

    本篇内容为 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 类图

    类图表现如下:

    image

    五、性能比较

    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 种实现方式,它们的用时比例应该大致相同。

  • 相关阅读:
    Oracle 网络
    Oracle 实例管理
    Oracle 体系结构四 逻辑和物理存储结构之间的关系
    Oracle 体系结构三 后台进程
    Oracle 体系结构二 内存结构
    Oracle 体系结构一 概述
    SQL 二
    SQL 一
    如何获取SQL Server数据库元数据的方法
    VB.NET中使用代表对方法异步调用
  • 原文地址:https://www.cnblogs.com/moonlightL/p/7798110.html
Copyright © 2011-2022 走看看