设计模式之单例模式

  无论什么开发中,设计模式都起着关键的作用,其中比较常用的当属单例了,所谓单例,就是让一个类在项目中只存在一个对象,即使用到这个类的地方很多,也只存在一个对象。但是为什么要这样呢,为什么只创建一个对象呢,多个不也行吗?这个就要结合实际来说了,有些对象我们确实只需要一个,比如说线程池、缓存、硬件设备,如果多个实例就可能会导致冲突,出现运行结果不一致的现象。

一、单例的基本框架

  当然,单例有很多种实现形式,最基本的框架大致如下:

 1 /**
 2  * 普通单例:
 3  * 只使用在单一线程,多线程时可能会出现多个对象
 4  * @author 刘伟 2015/10/13
 5  */
 6 public class SimpleSingleTon {
 7     private static SimpleSingleTon instance = null;
 8 
 9     public static SimpleSingleTon getInstance() {
10         if (instance == null) {
11             instance = new SimpleSingleTon();
12         }
13         return instance;
14     }
15 
16     private SimpleSingleTon() {
17     }
18 }

  上面代码即为单例的一个基本的实现形式,很轻松就能看出在程序中首次使用这个类的代码通过getInstance()方法就能实例化这个类从而获得类的对象,但以后再调用getInstance()时就不会实例化,而是直接获得已经存在的对象。

二、单例模式的优化

  这段代码在单一线程中执行是没有问题的,但如果是在多线程中就可能会出现两个或多个对象,试想一下如果恰好有两个线程同时进入了getIntance()得if语句里面,这时候就会实例化两次SimpleSingleTon,因为首次执行getInstance()时instance是null,所以这种情况是可能发生的。那么怎么避免这种情况发生呢,可以使用以下几种方法:

  • 方案一:急切创建对象

  这种方法是直接在单例类里面吧静态变量直接实例化,这样无论是多线程还是单线程都能保证只有一个对象了,缺点就是会对内存造成一定的浪费。

 1 /**
 2  * 单例优化--急切创建对象
 3  * 
 4  * @author codingblock 2015/10/13
 5  */
 6 public class SingleTon {
 7     private static SingleTon instance = new SingleTon();
 8 
 9     public static SingleTon getInstance() {
10         System.out.println("instance:" + instance);
11         return instance;
12     }
13 
14     private SingleTon() {
15 
16     }
17 }
  • 方案二:添加同步锁

  为了在多线程中不让两个线程同时执行getInstance()方法,可以为此方法添加一同步锁,这样就能避免此情况发生了。代码如下:

 1 /**
 2  * 单例优化--添加同步锁
 3  * 可以在多线程中运行
 4  * @author 刘伟 2015/10/13
 5  */
 6 public class SimpleSyncSingleTon {
 7     
 8     private static SimpleSyncSingleTon instance = null;
 9     
10     public static synchronized  SimpleSyncSingleTon getInstance() {
11         if (instance == null) {
12             instance = new SimpleSyncSingleTon();
13         }
14         return instance;
15     }
16     
17     private SimpleSyncSingleTon() {
18     }
19 }

  这样就可以保证在多线程中也只会创建一个对象,但同步锁是比较耗费资源的,如果在程序中频繁地获取对象,这样的话效率就大大地降低了。所以说,在单例中添加同步锁的方法比较适用于对对象获取不是很频繁地情况。

  • 方案三:双重检查加锁法

  首先需要在对象变量前面添加一个volatile关键字,这个是为了通知编译器线程安全用的。然后再getInstance检查两次,具体代码如下:

 1 /**
 2  * 单例优化--双重检查加锁法 
 3  * 可以在多线程中运行
 4  * @author 刘伟 2015/10/13
 5  */
 6 public class CheckAgainSingleTon {
 7 
 8     private volatile static CheckAgainSingleTon instance = null;
 9 
10     public static synchronized CheckAgainSingleTon getInstance() {
11         if (instance == null) {
12             synchronized (CheckAgainSingleTon.class) {
13                 if (instance == null) {
14                     instance = new CheckAgainSingleTon();
15                 }
16             }
17         }
18         return instance;
19     }
20 
21     private CheckAgainSingleTon() {
22     }
23 }

  通过这个方法程序在获取对象时无论怎么样都只会进入加锁区一次,例如最开始两个线程在竞争时,其中一个线程进入了加锁后创建了对象,以后所有的进程在执行getInstance方法时直接判断instance非null,就能直接返回对象了,不需要再进入加锁区了。这样即使程序频繁地获取对象也不会再进入加锁区了,相对第二种方法就大大节省了资源。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏轻量级微服务

微服务下跨语言 RPC 实现

目前主流的 Java 开发框架 Spring Boot,为了更方便集成 gRPC,自己开发了 spring-boot-starter-grpc,仅需简单的几行配...

1743
来自专栏林冠宏的技术文章

Golang 的 协程调度机制 与 GOMAXPROCS 性能调优

Golang 简称 Go,Go 的协程(goroutine) 和我们常见的线程(Thread)一样,拥有其调度器。

2021
来自专栏Petrichor的专栏

AttributeError: 'module' object has no attribute 'fullmatch'.

经过查找,发现出错的原因是 re库 中的 fullmatch函数 是 在py3.4之后才新添加的 。

2103
来自专栏CDN及云技术分享

GDB实现原理和使用范例

这篇文章为了让你深入了解gdb的工作原理,以及如何在linux环境下使用强大的gdb调试程序功能。

5081
来自专栏程序员的知识天地

Python使用os模块、Try语句、pathlib模块判断文件是否存在

通常在读写文件之前,需要判断文件或目录是否存在,不然某些处理方法可能会使程序出错。所以最好在做任何操作之前,先判断文件是否存在。

1102
来自专栏Python绿色通道

Python的进程

Python实现多进程的方式主要有两种:一种方法是使用os模块中的fork方法; 另一种是使用multiprocessing模块。这两种方法的区别在于前者仅适用...

652
来自专栏菩提树下的杨过

maven: 打包可运行的jar包(java application)及依赖项处理

IDE环境中,可以直接用exec-maven-plugin插件来运行java application,类似下面这样: 1 <plugin> 2 <g...

2069
来自专栏吴柯的运维笔记

【三剑客lcp】Linux基础命令行

绝对路径:cd /home/abc 相对路径:cd abc . 表示:当前路径 .. 表示:当前路径的上一层 没有...或以上的 LS命令 ls 查看当前路...

3226
来自专栏程序员互动联盟

【答疑释惑】getchar和getch

getchar有一个int型的返回值.当程序调用getchar时。程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲...

2747
来自专栏Python小屋

Python内置函数eval()用法及其安全问题

Python内置函数eval()用来对表达式进行求值: >>> eval('3+5') 8 >>> a = 3 >>> b = 5 >>> eval('a+b'...

8109

扫码关注云+社区