GOF设计模式之1:单例设计模式
1.单例设计模式核心作用:
保证一个类只有一个实例,并且提供了访问该实例的全局访问点
2.常见应用场景:
- window的任务管理器
- 项目中读取配置文件一般也是一个单例模式
- 数据库连接池的设计也是采用单例模式,因为数据库连接是一种数据库资源
- 操作系统的文件管理系统,也是单例模式,一个操作系统只能有一个文件系统
- Application也是单例的应用(Servlet编程或者Android的Application类)
- 在Spring中,每个bean默认也是单例的,这样的有点儿事Spring容器可以管理
- 在Servlet编程中每个Servlet也是单例的
- 在Spring MVC和Struts1框架中控制器对象也是单例
3.单例模式的优点
- 由于单例模式只生产一个对象,减少了系统开销,当一个对象的产生需要的资源比较多的时候,比如读取配置文件、产生其它依赖对象时,则可以在应用启动的时候直接产生一个单例对象,然后永久驻存内存的方式来解决。
- 单例模式可以在系统设置全局访问点,优化共享资源的访问。例如可以设计一个单例类,负责所有数据表的映射。
4.常见5中单例模式的实现方式:
主要
饿汉式:线程安全,调用效率高。但是不能延时加载
懒汉式:线程安全,调用效率不高。但是可以延迟加载
其它:
双重检锁式:由于JVM底层内部模型的原因,偶尔会出现问题,不建议使用
静态内部类式:线程安全,调用效率高,而且可以延迟加载
枚举单例:线程安全,调用效率高,不可延迟加载
饿汉式的示例代码:
public class Singleton01 {
//类初始化的时候,立即加载这个对象(没有延时加载的优势)。加载类时,是线程安全的
private static Singleton01 instance = new Singleton01();
private Singleton01(){}
//方法没有同步调用效率高
public static Singleton01 getInstance(){
return instance;
}
}
饿汉式单例模式的代码中,static变量会在类装载的时候进行初始化,此时不会涉及到多个线程对象访问该对象的问题。虚拟机会保证只会装载一次该类,肯定不会发生并发访问的问题,因此可以省略synchronized关键字
问题:如果仅仅是加载本类,而不是要调用getInstance,甚至永远都没有调用,则会造成资源浪费。
懒汉式的示例代码
package com.chunjiangchao.pattern.singleton;
/**
* 测试懒汉式单例模式
*/
public class Singleton02 {
//类初始化的时候,不初始化这个对象(延时加载,真正用的时候再创建)。
private static Singleton02 instance = null;
private Singleton02(){}
////方法同步,调用效率低!
public static synchronized Singleton02 getInstance(){
if(instance == null)
instance = new Singleton02();
return instance;
}
}
要点:延迟加载,懒加载真正用到的时候才会选择加载
问题:
资源利用率高了,但是每次调用getInstance()方法都要同步,并发效率较低。
双重检锁实现
package com.chunjiangchao.pattern.singleton;
/**
* 测试DCL(双重检锁)单例模式
*
*/
public class Singleton03 {
//类初始化的时候,不初始化这个对象(延时加载,真正用的时候再创建)。
private volatile static Singleton03 instance = null;
private Singleton03(){}
////代码块同步,调用效率要比同步方法要快一些,由于JVM的原因在高并发的情况下会出现问题
public static Singleton03 getInstance(){
if(instance == null){
synchronized (Singleton03.class) {
if(instance == null)
instance = new Singleton03();
}
}
return instance;
}
}
Volatile关键字的作用:
- 防止指令重排序如:instance = new Singleton03();这条操作分三步执行,1、分配内存;2、进行初始化;3、将生成对象的堆内存地址赋值给instance变量。这些指令中2、 3的位置可能会进行重排序,导致在获取到对象的时候,该对象还没有进行初始化。volatitle可以防止这种指令进行重排序。
- 当然Volatile还有一个作用是同步CPU缓存区和内存中的变量
提高了执行 的效率,不必每次获取对象的时候都要进行同步,只有第一次才会进行同步创建。
问题:
由于编译器优化的原因和JVM底层内部模型原因,偶尔会出现问题,不建议使用。但是我们可以在instance前面添加volatile关键字,这样就没问题了。
静态内部类实现方式:(懒加载方式)
package com.chunjiangchao.pattern.singleton;
/**
* 静态内部类单例模式
* 这种方式:线程安全,调用效率高,并且实现了延时加载!
*/
public class Singleton04 {
private Singleton04(){}
public static Singleton04 getInstance(){
return Inner.instance;
}
private static class Inner{
private static final Singleton04 instance = new Singleton04();
}
}
外部类没有static属性,则不会像饿汉式那样,上来就把对象造出来了
只有真正调用getInstance才会加载静态内部类。加载类时是线程安全的。instance 是static final类型,保证了内存中只有这样一个实例存在,而且只被赋值一次,从而保证了线程安全性。
兼并并发高效调用和延迟加载的优势。
换一句户说:静态内部有具备饿汉式和延迟加载的优势。
枚举实现单例:
package com.chunjiangchao.pattern.singleton;
/**
* 枚举式实现单例模式(没有延时加载)
*/
public enum Singleton05 {
instance;//这个枚举元素,本身就是单例对象!
public void operation(){
//添加需要的操作
}
}
优点:实现简单;枚举本身就是单例。由JVM从根本上提供保障。避免反射和序列化的漏洞
缺点:无延迟加载
5.如何选用这五种单例模式?
单例对象占用资源少,不需要延迟加载:
枚举好于饿汉式
单例对象占用资源大,需要延迟加载
静态内部类好于懒汉式
6.问题
反射可以破解上面(不包含枚举)的实现方式(防止的做法是在构造方法中手动抛出异常)
反序列化可以破解(不包含枚举)的实现方式
可以通过定义readResolve防止获得不同对象。反序列化的时候,如果对象所在的类定义了readResolve()方法(一种回调方法),返回自己创建的那个对象。
示例代码如下:
package com.bjsxt.singleton; import java.io.ObjectStreamException;
import java.io.Serializable; /**
* 测试懒汉式单例模式(如何防止反射和反序列化漏洞)
*
*/
public class SingletonDemo6 implements Serializable {
//类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
private static SingletonDemo6 instance; private SingletonDemo6(){ //私有化构造器
if(instance!=null){
throw new RuntimeException();
}
} //方法同步,调用效率低!
public static synchronized SingletonDemo6 getInstance(){
if(instance==null){
instance = new SingletonDemo6();
}
return instance;
} //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
private Object readResolve() throws ObjectStreamException {
return instance;
} }
测试代码如下:
package com.chunjiangchao.pattern.singleton; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor; /**
* 测试单例模式
*/
public class SingletonDemo {
public static void main(String[] args) throws Exception {
Singleton01 singleton01 = Singleton01.getInstance();
Singleton01 singleton02 = Singleton01.getInstance();
System.out.println(singleton01.hashCode());
System.out.println(singleton02.hashCode()); /**
//测试反射
try {
Class<?> clazz = Class.forName("com.chunjiangchao.pattern.singleton.Singleton06");
Constructor<?> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton06 singleton06 = (Singleton06) constructor.newInstance(null);
System.out.println(singleton06);
System.out.println(Singleton06.getInstance());
} catch (Exception e) {
e.printStackTrace();
}
//打印的结果:
//com.chunjiangchao.pattern.singleton.Singleton06@495c83b2
//com.chunjiangchao.pattern.singleton.Singleton06@58ca40be
*/
//通过序列化获取一个对象
Singleton06 instance = Singleton06.getInstance();
FileOutputStream fos = new FileOutputStream("/Users/xxx/Desktop/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.close();
fos.close(); FileInputStream fis = new FileInputStream("/Users/xxx/Desktop/a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
fis.close();
System.out.println(instance);
System.out.println(obj);
} }
7、测试几种模式的效率
五种单例模式在多线程环境下效率测试:使用CountDownLatch同步工具类,允许当前线程等待其它一组线程都执行完毕后,执行当前线程的后续操作。
countDown()当前线程执行此方法,计数器-1
await()调动此方法会一直阻塞当前线程,知道计数器为0的时候重新运行当前线程
下面示例代码来演示当前线程执行的效率:
package com.chunjiangchao.pattern.singleton; import java.util.concurrent.CountDownLatch; /**
* 五种单例模式的性能测试
*
*/
public class SingletonDemo02 { public static void main(String[] args) throws InterruptedException {
int threadNum = 10;
long beginTime = System.currentTimeMillis();
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
new Thread(new Runnable() { @Override
public void run() {
for (int j = 0; j < 20000; j++) {
//Singleton01.getInstance();//18ms
//Singleton02.getInstance();//49ms
//Singleton03.getInstance();//22ms
//Singleton04.getInstance();//32ms
Singleton05 instance = Singleton05.instance;//9ms
}
countDownLatch.countDown();
}
}).start();
}
//让主线程进行阻塞
countDownLatch.await();
long endTime = System.currentTimeMillis();
System.out.println(endTime-beginTime);
} }
单例对象占用资源少,不需要延迟加载:
GOF设计模式之1:单例设计模式的更多相关文章
- iOS单例设计模式具体解说(单例设计模式不断完好的过程)
在iOS中有非常多的设计模式,有一本书<Elements of Reusable Object-Oriented Software>(中文名字为<设计模式>)讲述了23种软件设 ...
- java23种设计模式之二: 单例设计模式(6种写法)
目的:在某些业务场景中,我们需要某个类的实例对象的只能有一个,因此我们需要创建一些单例对象. 本文共有6种写法,仅供参考 1.饿汉式 优点: 在多线程情况下,该方法创建的单例是线程安全的(立即加载) ...
- python之单例设计模式
设计模式之单例模式 单例设计模式是怎么来的?在面向对象的程序设计中,当业务并发量非常大时,那么就会出现重复创建相同的对象,每创建一个对象就会开辟一块内存空间,而这些对象其实是一模一样的,那么有没有办法 ...
- 单例设计模式 --c#
单例设计模式:在单例设计模式中我们要保持对象始终是唯一的 参考代码: class SingleObject { private SingleObject() { } private static Si ...
- Java面试 - 什么是单例设计模式,为什么要使用单例设计模式,如何实现单例设计模式(饿汉式和懒汉式)?
什么是单例设计模式? 单例设计模式就是一种控制实例化对象个数的设计模式. 为什么要使用单例设计模式? 使用单例设计模式可以节省内存空间,提高性能.因为很多情况下,有些类是不需要重复产生对象的. 如果重 ...
- Java单例设计模式的实现
1. 单例设计模式的定义 单例设计模式确保类只有一个实例对象,类本身负责创建自己的对象并向整个系统提供这个实例.在访问这个对象的时候,访问者可以直接获取到这个唯一对象而不必由访问者进行实例化. 单例设 ...
- 【iOS 单例设计模式】底层解析与运用
[iOS 单例设计模式]底层解析与运用 一.单例设计名词解释: (官方解释)单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例.(形象比喻)程序 — 公司 单例实例 - 管理 ...
- iOS开发——高级篇——iOS中常见的设计模式(MVC/单例/委托/观察者)
关于设计模式这个问题,在网上也找过一些资料,下面是我自己总结的,分享给大家 如果你刚接触设计模式,我们有好消息告诉你!首先,多亏了Cocoa的构建方式,你已经使用了许多的设计模式以及被鼓励的最佳实践. ...
- 单例设计模式全局缓存accessToken
使用微信JS-SDK开发的小伙伴们,看文档经常会看到这样一句话:(下面是微信开发文档的一部分原话截图) 这句话就是:开发者必须在自己的服务全局缓存access_token,jsapi_ticket 下 ...
随机推荐
- 【EasyUI】Combobox的联动和onChange/onSelect事件绑定
[效果图] (1)当选择“产品名称”这个查询项目时,运算条件只有“等于”和“不等于”,如下图所示. (2)当用户选择可以进行数值计算的查询项目时,运算条件就会有很多,如下图所示. [实现代码] 1.H ...
- VS2008 工程中部分文件不参与编译 从生成中排除【Worldsing笔记】
Visual Studio 2008 .VS2008.VC2008工程源文件配置.编译配置 有时编写代码时,往往存在这样的需求(或是希望有这样的功能):一个工程经过不共同的配置实现不同的版本或是功 ...
- POJ3321 Apple Tree (树状数组)
Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 16180 Accepted: 4836 Descr ...
- C#与C++对应的类型
//c++:HANDLE(void *) ---- c#:System.IntPtr //c++:Byte(unsigned char) ---- c ...
- GridView九宫图
了解下 <!--android:numColumns="auto_fit"--列数设置为自动: android:columnWidth="90dp"--每 ...
- jqeuery $.ajax 与后台jsone交互
============================action后台,我这里是SpringMVC================================================@C ...
- Objc基础学习记录2
1.[类 方法名]; //类方法,-静态成员函数, + (void)fun; 2.[对象名 方法名]; //实例方法, -非静态成员函数, - (void) fun; 3.带有冒号必须要有参数; 4. ...
- Linux下查看文件和文件夹大小的df和du命令
转自:http://www.yayu.org/look.php?id=162 当磁盘大小超过标准时会有报警提示,这时如果掌握df和du命令是非常明智的选择. df可以查看一级文件夹大小.使用比 ...
- UI:数据持久化
数据持久化 参考1 参考2 参考3 什么是数据持久化,就是将文件保存在本地的硬盘中,使得应用程序或者机器重启后可以继续访问以前保留的数据.IOS开发中有许多的数据持久化方案. 如下面五种方案 ...
- Spring aop expression
任意公共方法的执行:execution(public * *(..))任何一个名字以“set”开始的方法的执行:execution(* set*(..))AccountService接口定义的任意方法 ...