zoukankan      html  css  js  c++  java
  • 设计模式学习笔记(一、创建型-单例模式)

    目录:

    • 什么是单例模式
    • 为什么要使用单例模式
    • 如何实现单例模式

    什么是单例模式

    一个类只允许创建一个实例或对象,那么这个类就叫做单例类,这种设计模式就叫做叫做单例模式

    为什么要使用单例模式

    1、它可以解决资源访问冲突

    首先我们先看一段代码

     1 public class Logger {
     2 
     3     private FileWriter fileWriter;
     4 
     5     public Logger() throws IOException {
     6         File file = new File("E:\codeTest\log.txt");
     7         // true >>> 写入的字符追加到文件末尾
     8         fileWriter = new FileWriter(file, true);
     9     }
    10 
    11     public void log(String message) throws IOException {
    12         fileWriter.write(message);
    13     }
    14 }
     1 public class UserController {
     2 
     3     private Logger log = new Logger();
     4 
     5     public UserController() throws IOException {
     6     }
     7 
     8     public void login(String username, String password) throws IOException {
     9         log.log(MessageFormat.format("login username={0} password={1}", username, password));
    10     }
    11 }
     1 public class OrderController {
     2 
     3     private Logger log = new Logger();
     4 
     5     public OrderController() throws IOException {
     6     }
     7 
     8     public void find(long orderId) throws IOException {
     9         log.log(MessageFormat.format("find orderId={0}", orderId));
    10     }
    11 }

    乍一看正常情况下的确是没有问题的,但是如果是在高并发场景下,便可能会造成OrderController或UserController中追加到文件末尾的日志会覆盖掉另一方的输入。

    比如order和user同时竞争日志文件的资源,然后他们两个拿到的文件又是同一份;此时order向要在偏移量为100的位置添加文字“A”,user也要在偏移量为100的位置添加文字“B”。

    当order执行完后,偏移量为100的数据便为A,紧接着B也执行了同样的操作,所以便把偏移量100的数据给覆盖掉了。

    ———————————————————————————————————————————————————————

    解决问题的办法很多,最简单也可能最先想到的便是给log方法加锁了吧(因为是不同的对象,所以我们加类锁)。

     1 public class Logger {
     2 
     3     private FileWriter fileWriter;
     4 
     5     public Logger() throws IOException {
     6         File file = new File("E:\codeTest\log.txt");
     7         // true >>> 写入的字符追加到文件末尾
     8         fileWriter = new FileWriter(file, true);
     9     }
    10 
    11     public void log(String message) throws IOException {
    12         synchronized (Logger.class) {
    13             fileWriter.write(message);
    14         }
    15     }
    16 }

    当然加锁是可以达到目的的,但使用单例模式的话解决思路更简单。

    也不用创建那么多的Logger类,一方面可以节省内存空间,另一方面可以节省系统文件句柄。

     1 public class LoggerBuff {
     2 
     3     private FileWriter fileWriter;
     4     private static LoggerBuff INSTANCE;
     5 
     6     static {
     7         try {
     8             INSTANCE = new LoggerBuff();
     9         } catch (IOException e) {
    10             e.printStackTrace();
    11         }
    12     }
    13 
    14     public LoggerBuff() throws IOException {
    15         File file = new File("E:\codeTest\log.txt");
    16         // true >>> 写入的字符追加到文件末尾
    17         fileWriter = new FileWriter(file, true);
    18     }
    19 
    20     public LoggerBuff getInstance() {
    21         return INSTANCE;
    22     }
    23 
    24     public void log(String message) throws IOException {
    25         synchronized (LoggerBuff.class) {
    26             fileWriter.write(message);
    27         }
    28     }
    29 }

    ———————————————————————————————————————————————————————

    2、它可以表示全局唯一的类

    从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。

    再比如,唯一递增ID号码生成器,如果程序中有两个对象,那就会存在生成重复ID的情况。所以,我们应该将ID生成器类设计为单例。

    如何实现单例模式

    • 构造函数需要是private访问权限的,这样才能避免外部通过new创建实例。
    • 考虑对象创建时的线程安全问题
    • 考虑是否支持延迟加载
    • 考虑getInstance()性能是否高(是否加锁)。

    1、饿汉:实例对象为静态,启动程序时加载。

     1 public class HungryMan {
     2     
     3     private AtomicLong id = new AtomicLong(0);
     4     private static final HungryMan INSTANCE = new HungryMan();
     5 
     6     private HungryMan() {
     7     }
     8 
     9     public static HungryMan getInstance() {
    10         return INSTANCE;
    11     }
    12 
    13     public long getId() {
    14         return id.incrementAndGet();
    15     }
    16 }

    适用初始化占用资源较多消耗时间过长,因为如果是这两种情况还是用的懒加载的话便会影响系统性能。

    ———————————————————————————————————————————————————————

    2、懒汉:懒加载。

     1 public class LazyMan {
     2 
     3     private AtomicLong id = new AtomicLong(0);
     4     private static LazyMan instance;
     5 
     6     private LazyMan() {
     7     }
     8 
     9     public static synchronized LazyMan getInstance() {
    10         if (instance == null) {
    11             instance = new LazyMan();
    12         }
    13         return instance;
    14     }
    15 
    16     public long getId() {
    17         return id.incrementAndGet();
    18     }
    19 }

    支持懒加载,但并发度低。

    ———————————————————————————————————————————————————————

    3、双重检测:既支持懒加载,又支持高并发的单例实现。

     1 public class Double {
     2 
     3     private AtomicLong id = new AtomicLong(0);
     4     private static Double instance;
     5 
     6     private Double() {
     7     }
     8 
     9     public static Double getInstance() {
    10         if (instance == null) {
    11             synchronized (Double.class) {
    12                 if (instance == null) {
    13                     instance = new Double();
    14                 }
    15             }
    16         }
    17         return instance;
    18     }
    19 
    20     public long getId() {
    21         return id.incrementAndGet();
    22     }
    23 }

    ———————————————————————————————————————————————————————

    4、静态内部类:比双重检测更优雅的方式。

     1 public class Inner {
     2 
     3     private AtomicLong id = new AtomicLong(0);
     4 
     5     private Inner() {
     6     }
     7 
     8     private static class SingletonHolder {
     9         private static final Inner INSTANCE = new Inner();
    10     }
    11 
    12     public static Inner getInstance() {
    13         return SingletonHolder.INSTANCE;
    14     }
    15 
    16     public long getId() {
    17         return id.incrementAndGet();
    18     }
    19 }

    ———————————————————————————————————————————————————————

    5、枚举:利用枚举特性实现单例。

     1 public enum Enum {
     2     
     3     INSTANCE;
     4 
     5     private AtomicLong id = new AtomicLong(0);
     6 
     7     public long getId() {
     8         return id.incrementAndGet();
     9     }
    10 }
  • 相关阅读:
    Android中Alarm的机制
    String字符串操作--切割,截取,替换,查找,比较,去空格.....
    时间类(时间戳的各种转换成)
    android 常用时间格式转换代码
    Android时间戳与字符串相互转换
    Android时间对话框TimePickerDialog介绍
    一种基于Qt的可伸缩的全异步C/S架构server实现(一) 综述
    C++ overloading contructor
    特征生成
    Atitit.软件仪表盘(2)--vm子系统--资源占用监測
  • 原文地址:https://www.cnblogs.com/bzfsdr/p/12632279.html
Copyright © 2011-2022 走看看