博客信息

单例模式Singleton(创建模式)

发布时间:『 2019-03-21 22:50』  博客类别:23种设计模式  阅读(732)


案例

这里以经典的数据库驱动连接工具类为例;

数据库连接工具类在各个业务的dao层会被初始化调用;

而每一次初始化都会在堆内存中申请一片空间,这是对堆内存资源的浪费;

如果在堆内存只开辟一片空间,各个业务的Dao层建立不同的引用进行操作,就可以资源利用最大化;

单例设计模式就应运而生,它就是为了解决对象初始化,造成资源浪费的情况;


小李飞刀_设计模式

术语

单例:Singleton

小李飞刀_设计模式


  • 分类

    • 饿汉式(静态常量)

    • 饿汉式(静态代码块)

    • 懒汉式(线程不安全)

    • 懒汉式(线程安全,同步代码块)

    • 懒汉式(线程安全,同步方法)

    • 双重检查

    • 静态内部类

    • 枚举

package com.javaxl.design.single.after;

/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create  2020-02-21 17:09
* 饿汉式(静态常量)
*/
public class DBAccess {
  //   构造器私有化,避免外部创建对象
  private DBAccess() {

  }

  //   static修饰,保障其能够被静态方法访问
  private final static DBAccess dbAccess = new DBAccess();

  //   外部直接调用静态方法实例化对象
  public static DBAccess getInstance() {
      return dbAccess;
  }

}

/**
* 饿汉式(静态代码块)
*/
class DBAccess2 {
  private DBAccess2() {

  }

  private static DBAccess2 dbAccess = null;

  static {
      dbAccess = new DBAccess2();
  }

  public static DBAccess2 getInstance() {
      return dbAccess;
  }
}

/**
* 懒汉式(线程不安全)
*/
class DBAccess3 {
  private DBAccess3() {

  }

  private static DBAccess3 dbAccess = null;

  public static DBAccess3 getInstance() {
      if (dbAccess == null) {
          dbAccess = new DBAccess3();
      }
      return dbAccess;
  }
}

/**
* 懒汉式(同步代码块)
*/
class DBAccess4 {
  private DBAccess4() {

  }

  private static DBAccess4 dbAccess = null;

  public static DBAccess4 getInstance() {
      synchronized (DBAccess4.class) {
          if (dbAccess == null) {
              dbAccess = new DBAccess4();
          }
      }
      return dbAccess;
  }
}

/**
* 懒汉式(线程安全,同步方法)
*/
class DBAccess5 {
  private DBAccess5() {

  }

  private static DBAccess5 dbAccess = null;

  public synchronized static DBAccess5 getInstance() {
      if (dbAccess == null) {
          dbAccess = new DBAccess5();
      }
      return dbAccess;
  }
}

/**
* 双重检查
*/
class DBAccess6 {
  private DBAccess6() {

  }

  private static DBAccess6 dbAccess = null;

  public static DBAccess6 getInstance() {
      if (dbAccess == null) {
          synchronized (DBAccess6.class) {
              if (dbAccess == null) {
                  dbAccess = new DBAccess6();
              }
          }
      }
      return dbAccess;
//       return new DBAccess6();
  }
}

/**
* 静态内部类
*/
class DBAccess7 {
  private DBAccess7() {

  }

  private static class DBAccess7Instance{
      private static DBAccess7 dbAccess = new DBAccess7();
  }

  public static DBAccess7 getInstance() {
      return DBAccess7Instance.dbAccess;
  }
}

/**
* 枚举
*/
enum DBAccess8{
  DBACCESS;
  public static DBAccess8 getInstance() {
      return DBAccess8.DBACCESS;
  }
}

结论:

单例中两种饿汉式可用,但是存在性能问题

单例中三种懒汉式不推荐,存在线程安全问题,同步方法的方式解决了线程的问题,但是性能极差

最后三种单例模式值得推荐

  • 注意事项

    • 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

    • 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new

  • 应用

    • jdk源码中Runtime类

    • tomcat中ApplicationContext类

    • session 工厂



建议单例使用饿汉式,代码更加简洁,



单例模式的使用场景

这里举个简单的例子,网站访问量统计

在不考虑设计模式这一因素的情况下,我们最初是怎么统计网站统计量的呢?

这里需要写一个监听类、两个servlet来、一个JSP页面模拟网站访问统计的场景

public class InitListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        context.setAttribute("count",0);
    }
}


DemoServlet1.java

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = req.getServletContext();
        int count = (int) context.getAttribute("count");
        context.setAttribute("count",++count);
        req.getRequestDispatcher("/index.jsp").forward(req,resp);
    }


DemoServlet2.java

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = req.getServletContext();
        int count = (int) context.getAttribute("count");
        context.setAttribute("count",++count);
        req.getRequestDispatcher("/index.jsp").forward(req,resp);
    }


index.jsp

全局作用域统计网站访问量:${count}


那么不使用全局作用域,我们能否统计网站的访问量呢?

答案是肯定的,servlet中全局作用域之所以能够统计网站的访问量,缘由在于servlet容器是单例的,工程一旦启动完毕,application作用域是只有一个的,

关于网站中的所有请求用到的application都是同一个,所以我们可以那它用来统计访问量。参照这一原理,我们可以用单例模式来统计网站访问量。代码

如下:

这里可以不需要监听类

两个servlet代码如下,代码完全一致

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = req.getServletContext();
        int count = (int) context.getAttribute("count");

        Singleton singleton = Singleton.getInstance();
        singleton.setCount(singleton.getCount()+1);

        context.setAttribute("count",++count);
        req.setAttribute("count2",singleton.getCount());
        req.getRequestDispatcher("/index.jsp").forward(req,resp);
    }


index.jsp

全局作用域统计网站访问量:${count}
单例模式统计网站访问量:${count2}


不管是移动端、还是电脑端,只要同一局域网,那么访问

http://localhost:8080/design/demo1

http://localhost:8080/design/demo2

都可以达到访问量+1的效果

小李飞刀_设计模式



关键字:     设计模式  

备案号:湘ICP备19000029号

Copyright © 2018-2019 javaxl晓码阁 版权所有