你真能说清楚单例模式吗?
Posted 码神手记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你真能说清楚单例模式吗?相关的知识,希望对你有一定的参考价值。
码神手记——资深攻城狮的私房笔记。微信公众平台/知乎/头条/简书同步发文。感谢关注与转发。
核心作用
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见应用场景
Windows的Task Manager(任务管理器)是很典型的单例模式
Windows的Recycle Bin(回收站)是单例应用。在系统运行中,回收站只维护仅有的一个实例。
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据都new一个对象去读取。
网站的计数器,一般也是采用单例模式实现,否则难以同步。
应用程序的日志应用,一般都使用单例模式实现,一般因为共享的日志文件一直处于打开状态,只能有一个实例去操作,否则内容不好追加。
数据库连接池的设计一般也采用单例模式。
操作系统的文件系统,也是单例模式实现的具体例子,一个操作系统只能有一个文件系统。
在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理。
在Servlet编程中,每个Servlet也是单例。
在Spring MVC框架/struts1框架中,控制器对象也是单例。
单例模式的优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其它依赖对象,则可以在应用启动时直接产生一个单例对象,然后永久驻留内存。
单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
常见的五种单例模式实现方式
饿汉式(线程安全,调用效率高。但是,不能延时加载)
懒汉式(线程安全,调用效率不高。但是,可以延时加载)
双重检测锁式(由于指令重排,在多线程环境下可能出现引用不空,但对象未初始化。不建议使用。)
静态内部类式(线程安全、调用效率高,可以延时加载)
枚举单例(线程安全,调用效率高,不能延时加载)
如何选用?
单例对象占用资源少,不需要延时加载:枚举式 好于饿汉式
单例对象占用资源大,需要延时加载:静态内部类式,好于懒汉式
代码示例
饿汉式
package com.liu.singleton;
/**
* 测试饿汉式单例模式
*
* @author Steve
*
*/
public class SingletonDemo01 {
//类初始化时,立即加载(没有延时加载的优势)。加载类时是线程安全的。
private static SingletonDemo01 instance = new SingletonDemo01();
private SingletonDemo01() {
//避免使用反射调用构造器时创建新的对象
if (instance !=null) {
throw new RuntimeException("请使用getInstance方法获取对象" );
}
}
//方法没有同步,调用效率高
public static SingletonDemo01 getInstance() {
return instance ;
}
// 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
private Object readResolve() throws ObjectStreamException {
return instance ;
}
}
懒汉式
package com.liu.singleton;
/**
* 测试懒汉式单例模式
*
* @author Steve
*
*/
public class SingletonDemo02 {
private static SingletonDemo02 instance ;
private SingletonDemo02() {
//避免使用反射调用构造器时创建新的对象
if (instance !=null) {
throw new RuntimeException("请使用getInstance方法获取对象" );
}
}
public static synchronized SingletonDemo02 getInstance() {
if (null == instance) {
instance = new SingletonDemo02 ();
}
return instance ;
}
// 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
private Object readResolve() throws ObjectStreamException {
return instance ;
}
}
双重检查锁式
package com.liu.singleton;
/**
* 双重检查锁实现单例模式
*
* @author Steve
*
*/
public class SingletonDemo03 {
private static SingletonDemo03 instance = null;
private SingletonDemo03() {
//避免使用反射调用构造器时创建新的对象
if (instance !=null) {
throw new RuntimeException("请使用getInstance方法获取对象" );
}
}
public static SingletonDemo03 getInstance() {
if (null == instance) {
SingletonDemo03 sc;
synchronized (SingletonDemo03.class) {
sc = instance;
if (sc == null ) {
synchronized (SingletonDemo03.class) {
if (sc == null ) {
sc = new SingletonDemo03();
}
}
instance = sc;
}
}
}
return instance ;
}
// 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
private Object readResolve() throws ObjectStreamException {
return instance ;
}
}
静态内部类式
package com.liu.singleton;
/**
* 静态内部类实现单例模式
*
* 外部类没有static属性,则不会像饿汉式那样立即加载对象
*
* 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的,由JVM保证。
*
* instance是static final类型,保证了在内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。
*
* 兼备了并发高效调用和延迟加载的优势
*
* @author Steve
*
*/
public class SingletonDemo04 {
private static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo04() {
//避免使用反射调用构造器时创建新的对象
if (instance !=null) {
throw new RuntimeException("请使用getInstance方法获取对象" );
}
}
// 反序列化时,如果定义了该方法,则直接返回此方法指定的对象,不会再单独创建对象
private Object readResolve() throws ObjectStreamException {
return SingletonClassInstance.instance ;
}
}
枚举式
package com.liu.singleton;
/**
* 使用枚举实现单例模式
*
* 优点:实现简单、枚举本身是单例模式。由JVM从根本上提供保障,避免通过反射和反序列化创建对象的漏洞
*
* 缺点:无延迟加载
*
* @author Steve
*
*/
public enum SingletonDemo05 {
/**
* 这个枚举元素,本身就是单例的
*/
INSTANCE;
/**
* 添加自己需要的操作
*/
public void singletonOperation() {
}
}
多线程测试各种实现方式的效率
package com.liu.singleton;
import java.util.concurrent.CountDownLatch;
/**
* 使用CountDownLatch测试多线程环境下各个单例模式实现方式的效率
*
* @author Steve
*
*/
public class Client3 {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = SingletonDemo01.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await(); // main线程阻塞,知道计数器值变为0,才会继续往下执行
long end = System.currentTimeMillis();
System. out.println("总耗时:" + (end - start));
}
}
码神手记——资深攻城狮的私房笔记。微信公众平台/知乎/头条/简书同步发文。感谢关注与转发。
以上是关于你真能说清楚单例模式吗?的主要内容,如果未能解决你的问题,请参考以下文章
面试高频题:Spring和SpringMvc父子容器你能说清楚吗