一、单例模式概述

  保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

  由于单例模式只生成一个实例,减少了系统性能开销。所以当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

  单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,复制所有数据表的映射处理。

  1. 懒汉式

    1)线程不安全

    ① 构造器私有化,避免外部直接创建对象

    ② 声明一个私有的静态属性

    ③ 对外创建一个公共的静态方法访问该属性

      如果属性没有该对象,创建该对象

    2)线程安全

      使用同步方法

    3)双重检测(线程安全)double checking DCL

      使用同步块

      好处:第一次是为了不必要的同步,第二次是在属性等于null的情况下才创建实例
  2. 饿汉式

    ① 构造器私有化,避免外部直接创建对象

    ② 声明一个私有的静态属性,同时创建该对象

    ③ 对外创建一个公共的静态方法访问该属性

      好处:类加载时就已经加载该类对象,当需要该对象时,若没有则创建,若有直接返回

  3. 静态内部类

    ① 构造器私有化,避免外部直接创建对象

    ② 使用静态内部类声明一个私有的静态属性,同时创建该对象

    ③ 对外创建一个公共的静态方法访问该属性

      好处:不调用静态方法不加载内部类,延缓加载,提高效率

  4.枚举

    枚举式 (jdk1.4及以前建议使用)

二、单例模式的五种写法

  1.饿汉式

    优点:线程安全,效率高

    缺点:无法延时加载

 public class Singleton {

     private static Singleton instance = new Singleton();

     //私有化构造器
private Singleton() {} //提供全局访问点
public static Singleton getInstance() {
return instance;
} }
 public class Singleton {

     private static Singleton instance = null;

     static {
instance = new Singleton();
} //私有化构造器
private Singleton() {} //提供全局访问点
public static Singleton getInstance() {
return instance;
} }

  2. 懒汉式

    优点:线程安全,延时加载

    缺点:效率较低

    (1)非线程安全

 public class Singleton {

     private static Singleton instance;

     //私有化构造器
private Singleton() {} //提供一个全局的访问点
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
} }

    (2)线程安全

 public class Singleton {

     private static Singleton instance;

     //私有化构造器
private Singleton() {} //使用同步方法获取该类对象
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
} }
 public class Singleton {

     private static Singleton instance;

     //私有化构造器
private Singleton() {} //使用同步块获取该类对象
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
} }

  3.双重检查锁

    注意:由于编译器优化和JVM底层内部模型原因,偶尔会出问题,不建议使用

    优点:线程安全,延时加载

    缺点:效率较低,会出错误

 public class Singleton {

     private static Singleton instance;

     private Singleton() {}

     //第一次判断是为了避免不必要的同步,第二次判断是属性为null时创建实例
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
} }

  4.静态内部类式

    不调用静态方法不加载内部类,延缓加载(懒加载),提高效率

    优点:线程安全,延时加载,效率高

 public class Singleton {

     //静态内部类
private static class SingletonHolder {
//final可加可不加,因为外部类的外部无法使用该内部类
private static /*final*/ Singleton instance = new Singleton();
} //私有化构造器
private Singleton() {} //提供一个全局的访问点
public static Singleton getInstance() {
return SingletonHolder.instance;
} }

  5.枚举

    注意:建议使用

    优点:实现简单,线程安全,效率高,由于JVM从根本上实现保障,避免反射和反序列化的漏洞

    缺点:无延时加载

 1 public enum Singleton {
2 //这个枚举元素,本身就是一个单例对象
3 INSTANCE;
4 }

三、测试五种单例模式的耗时问题

 /**
在多线程环境下测试使用单例设计模式时创建对象的耗时(100个线程创建10000个对象)
饿汉式:15ms
懒汉式:671ms
双重检测锁:65ms
静态内部类:23ms
枚举:32ms
* @author CL
*
*/
public class TestRuntime { public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis(); int threadNum = 100;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
//匿名内部类
for (int i = 0; i < threadNum; i++) {
new Thread(new Runnable() { @Override
public void run() {
for(int i = 0; i < 10000; i++) {
//依次测试
Object o1 = SingletonDemo01.getInstance(); //饿汉式
// Object o2 = SingletonDemo02.getInstance(); //懒汉式
// Object o3 = SingletonDemo03.getInstance(); //双重检查锁
// Object o4 = SingletonDemo04.getInstance(); //静态内部类
// Object o5 = SingletonDemo05.INSTANCE; //枚举
}
}
}).start();
countDownLatch.countDown(); //递减锁存器的计数,如果计数到达零,则释放所有等待的线程
} countDownLatch.await(); //阻塞main线程,知道计数器为0才开始继续执行 long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end - start)+"ms");
} }

  注意,根据电脑配置等因素时间会有差异,但时间比大致相同。

  如何选用单例模式?

    (1)单例对象占用资源少,不需要延时加载时:枚举式好于饿汉式

    (2)单例对象占用资源大,需要延时加载时:静态内部类式好于懒汉式

四、破解单例模式 

  1.使用反序列化破解单例模式

    步骤:(1)使用序列化将已经创建的对象写出到系统文件

          (2)使用反序列化读取文件中的对象

 import java.io.Serializable;

 /**
* 使用反序列化破解单例模式
* @author CL
*
*/
public class Singleton implements Serializable { private static class SingletonHolder {
private static Singleton instance = new Singleton();
} private Singleton() {} public static Singleton getInstance() {
return SingletonHolder.instance;
} }
 import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; /**
* 测试反序列化破解单例模式
* @author CL
*
*/
public class TestSingleton { public static void main(String[] args) throws Exception {
//先获得一个对象
Singleton s01 = Singleton.getInstance();
System.out.println("先获取的对象:" + s01); //(1)使用序列化将对象写出到系统文件
String filePath = "C:\\file\\obj.txt";
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filePath));
oos.writeObject(s01);
oos.flush();
oos.close(); //(2)使用反序列化读取文件中的对象
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filePath));
Singleton s02 = (Singleton) ois.readObject();
ois.close(); System.out.println("反序列化读取的对象:" + s02);
} }

  控制台输出:

先获取的对象:com.caolei.singleton.Singleton@15db9742
反序列化读取的对象:com.caolei.singleton.Singleton@33909752

  显然已经通过反序列化已经创建了新的对象,解决办法是在类中添加如下代码:

 /**
* 反序列化时,如果创建了readResolve()方法则直接返回已经创建好的对象,而不需要再重新创建新的对象
* @return
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException {
return instance;
}

  现在再测试:

 import java.io.ObjectStreamException;
import java.io.Serializable; /**
* 使用反序列化破解单例模式
* @author CL
*
*/
public class Singleton implements Serializable { private static class SingletonHolder {
private static Singleton instance = new Singleton();
} private Singleton() {} public static Singleton getInstance() {
return SingletonHolder.instance;
} //反序列化时,如果创建了readResolve()方法则直接返回已经创建好的对象,而不需要再重新创建新的对象
private Object readResolve() throws ObjectStreamException {
return SingletonHolder.instance;
} }
 import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; /**
* 测试反序列化破解单例模式
* @author CL
*
*/
public class TestSingleton { public static void main(String[] args) throws Exception {
//先获得一个对象
Singleton s01 = Singleton.getInstance();
System.out.println("先获取的对象:" + s01); //(1)使用序列化将对象写出到系统文件
String filePath = "C:\\file\\obj.txt";
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filePath));
oos.writeObject(s01);
oos.flush();
oos.close(); //(2)使用反序列化读取文件中的对象
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filePath));
Singleton s02 = (Singleton) ois.readObject();
ois.close(); System.out.println("反序列化读取的对象:" + s02);
} }

  控制台输出:

先获取的对象:com.caolei.singleton.Singleton@15db9742
反序列化读取的对象:com.caolei.singleton.Singleton@15db9742

  现在创建的对象是同一对象,避免了反序列化破解单例模式出现的问题。

  2.使用反射破解单例模式

    步骤:(1)获取对象

          (2)获取构造器

          (3)跳过安全检查

          (4)创建对象

 /**
* 使用反射破解单例模式
* @author CL
*
*/
public class Singleton { private static class SingletonHolder {
private static Singleton instance = new Singleton();
} private Singleton() {} public static Singleton getInstance() {
return SingletonHolder.instance;
} }
 import java.lang.reflect.Constructor;

 /**
* 测试反射破解单例模式
* @author CL
*
*/
public class TestSingleton { public static void main(String[] args) throws Exception {
Singleton s1 = Singleton.getInstance();
System.out.println("先获取的对象:" + s1); //使用反射破解单例模式
//(1)创建对象
Class<Singleton> clazz = (Class<Singleton>) Class.forName("com.caolei.singleton.Singleton");
//(2)获得构造器
Constructor<Singleton> c = clazz.getDeclaredConstructor(null);
//(3)跳过安全检查
c.setAccessible(true);
//(4)创建对象
Singleton s2 = c.newInstance(); System.out.println("反射获取的对象:" + s2);
}
}

  控制台输出:

先获取的对象:com.caolei.singleton.Singleton@15db9742
反射获取的对象:com.caolei.singleton.Singleton@6d06d69c

  显然使用反射机制跳过安全检查通过私有的构造器创建了新的对象,解决办法是在私有的构造器中添加如下代码:

 if (instance != null) {
throw new RuntimeException(); //再次创建对象时抛出异常
}

  现在再测试:

 /**
* 使用反射破解单例模式
* @author CL
*
*/
public class Singleton { private static class SingletonHolder {
private static Singleton instance = new Singleton();
} private Singleton() {
if (SingletonHolder.instance != null) {
throw new RuntimeException(); //再次创建对象时抛出异常
}
} public static Singleton getInstance() {
return SingletonHolder.instance;
} }
 import java.lang.reflect.Constructor;

 /**
* 测试反射破解单例模式
* @author CL
*
*/
public class TestSingleton { public static void main(String[] args) throws Exception {
Singleton s1 = Singleton.getInstance();
System.out.println("先获取的对象:" + s1); //使用反射破解单例模式
//(1)创建对象
Class<Singleton> clazz = (Class<Singleton>) Class.forName("com.caolei.singleton.Singleton");
//(2)获得构造器
Constructor<Singleton> c = clazz.getDeclaredConstructor(null);
//(3)跳过安全检查
c.setAccessible(true);
//(4)创建对象
Singleton s2 = c.newInstance(); System.out.println("反射获取的对象:" + s2);
}
}

  控制台输出:

先获取的对象:com.caolei.singleton.Singleton@15db9742
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at com.caolei.singleton.TestSingleton.main(TestSingleton.java:24)
Caused by: java.lang.RuntimeException
at com.caolei.singleton.Singleton.<init>(Singleton.java:16)
... 5 more

五、单例模式常见的应用场景

  (1)Windows的Task Manager(任务管理器)、Recycle Bin(回收站)、文件系统等都是典型的单例模式;

  (2)在项目中,读取配置文件的类一般也只有一个对象,没有必要每次使用配件的数据时,都去new一个对象来获取;

  (3)网站的计数器,一般也是采用单例模式实现,否则难以同步;

  (4)数据库连接池的设计就是采用单例模式;

  (5)在Spring中,每个Bean默认是单例的,便于Spring容器管理;

  (6)每个Servlet都是单例的;

  (7)………………

GOF23设计模式之单例模式(singleton)的更多相关文章

  1. 设计模式之单例模式——Singleton

                        设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有 ...

  2. 设计模式(4) -- 单例模式(Singleton)

    设计模式(4)  -- 单例模式(Singleton) 试想一个读取配置文件的需求,创建完读取类后通过New一个类的实例来读取配置文件的内容,在系统运行期间,系统中会存在很多个该类的实例对象,也就是说 ...

  3. 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)

    原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...

  4. 【设计模式】单例模式-Singleton

    [设计模式]单例模式-SingletonEnsure a class has only one instance, and provide a global point to access of it ...

  5. 【GOF23设计模式】单例模式

    来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_单例模式.应用场景.饿汉式.懒汉式 1.GOF23设计模式  2.单例模式  3.饿汉式  1 package com.t ...

  6. 设计模式之——单例模式(Singleton)的常见应用场景

    单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此 ...

  7. 设计模式之单例模式(Singleton Pattern)

    单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...

  8. 设计模式一: 单例模式(Singleton)

    简介 单例模式是属于创建型模式的一种(另外两种分别是结构型模式,行为型模式).是设计模式中最为简单的一种. 英文单词Singleton的数学含义是"有且仅有一个元素的集合". 从实 ...

  9. 设计模式之——单例模式(Singleton)的常见应用场景(转):

    单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此 ...

  10. java设计模式之 单例模式 Singleton

    static 的应用 单例模式 Singleton 单例:保证一个类在系统中最多只创建一个实例. 好处:由于过多创建对象实例,会产生过多的系统垃圾,需要GC频繁回收,由于GC会占用较大的系统资源,所有 ...

随机推荐

  1. Memcached 连接

    我们可以通过 telnet 命令并指定主机ip和端口来连接 Memcached 服务. 语法 telnet HOST PORT 命令中的 HOST 和 PORT 为运行 Memcached 服务的 I ...

  2. virtio guest side implementation: PCI, virtio device, virtio net and virtqueue

    With the publishing of OASIS virtio specification version 1.0, virtio made another big step in becom ...

  3. ButterKnife使用详谈

    (1)ButterKnife是什么? 在开发过程中,我们总是会写大量的findViewById和点击事件,像初始view.设置view监听这样简单而重复的操作让人觉得特别麻烦,当然不会偷懒的程序员不是 ...

  4. mongodb笔记(一) 分片 &&文档连接

    版本:mongodb3.4 ; 分片: 工作顺序:router=>config=>shards 一,配置config: 3.4中config必须为replSet.下面配置两个config. ...

  5. ng2 学习笔记(二)表单及表单验证

    在上一篇文章中提到了表单,只说了表单的数据绑定,这一篇文章主要讲一下表单验证,为什么把表单单独拿出来学习,主要是因为,表单是商业应用的支柱,我们用它来执行登录.求助.下单.预订机票.安排会议,以及不计 ...

  6. java reflect反射---Java高级开发必须懂的

    理解反射对学习Java框架有很大的帮助,如Spring框架的核心就是使用Java反射实现的,而且对做一些Java底层的操作会很有帮助.  一.Class类的使用         1.万事万物皆对象,( ...

  7. centos下使用fdisk扩展分区容量大小

    硬盘空间为20G,VMware增加磁盘大小,需要再增加10G空间 扩展完后,重启系统,再次使用fdisk -l查看,会发现硬盘空间变大了: 重新创建分区,调整分区信息 本次实验主要对/dev/sda4 ...

  8. 一个较好的style与ControlTemplate结合的示例(以Button为例)

    <!--按钮背景画刷-->    <LinearGradientBrush x:Key="buttonBackgroundBrush">        &l ...

  9. base64编码的 文件 图片

    //图片 转为 base64编码的文本 private void button1_Click(object sender, EventArgs e) { OpenFileDialog dlg = ne ...

  10. Ajax技术基础

    对于AJAX技术,感觉以前虽然用过但是对于AJAX没有一个清晰的认识,今天专门总结一下,希望掌握的更加牢固吧! 一,什么是AJAX? AJAX通常被叫做异步刷新技术,其实他也是可以同步的.主要都用于异 ...