设计模式学习笔记(一、创建型-单例模式)

目录:

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

什么是单例模式

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

为什么要使用单例模式

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 }
posted @ 2020-04-04 16:30  被猪附身的人  阅读(187)  评论(0编辑  收藏  举报