设计模式之单例模式

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

一、单例的基本框架

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

 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 条评论
登录 后参与评论

相关文章

来自专栏Spark学习技巧

常见的几种单例模式

874
来自专栏Java3y

JSP第五篇【JSTL的介绍、core标签库、fn方法库、fmt标签库】

什么是JSTL JSTL全称为 JSP Standard Tag Library 即JSP标准标签库。 JSTL作为最基本的标签库,提供了一系列的JSP标签,实...

2795
来自专栏对角另一面

读Zepto源码之Stack模块

Stack 模块为 Zepto 添加了 addSelf 和 end 方法。 读 Zepto 源码系列文章已经放到了github上,欢迎star: reading...

2010
来自专栏向治洪

android 开发Handler源码剖析

Android的消息机制主要是Handler的运行机制,而讲Handler的机制,又需要和MessageQueue和Looper结合。MessageQueue中...

1737
来自专栏增长技术

ConcurrentModificationException

983
来自专栏技术专栏

慕课网高并发实战(五)- 安全发布对象

不正确的发布可变对象导致的两种错误: 1.发布线程意外的所有线程都可以看到被发布对象的过期的值 2.线程看到的被发布对象的引用是最新的,然而被发布对象的状态...

902
来自专栏kalifaの日々

C语言中static,const和static const 的区别

在第一次调用test()时,如果static int b没有被我赋初值,也会被默认赋值成0。然后执行自增运算,所以输出1。第二次调用test()时如果是普通的变...

451
来自专栏十月梦想

js常量

1042
来自专栏java小白

LinkedHashMap的accessOrder

1729
来自专栏Java帮帮-微信公众号-技术文章全总结

Java面试系列9

✎一、Java有没有goto? java中的保留字,现在没有在java中使用。 ✎二、必须要知道的运行时异常 ArithmeticException 是...

2534

扫码关注云+社区