如何创建一个完美的单例模式

单例模式的目的是什么

单例类的目的是控制对象创建,约束对象的数量有且只有一个。单例模式只允许有一个入口来创建类的实例。

因为只有一个单例类的实例,任何单例类的实例都将之会产生一个类,就像静态域。当你需要控制资源的时候,如何数据库连接池、线程池或者使用sockets,单例模式是非常有用的。

下面我们来创建一个单例类。

创建单例类

为了实现单例类,最简单的方式就是将构造器私有化设置为private。有两种初始化方式

饿汉式

饿汉式初始化,单例类的实例在类加载的时候被创建,这是创建单例类最简单的方法。

通过将构造器声明为private,不允许其他类来创建单例类实例。取而代之的是,创建一个静态方法(一般命名为getInstance)来提供创建类实例的唯一入口。

 1  package com.net;
 2 /**
 3  * Created by admin on 2017/9/28.
 4  */
 5 public class SingletonClass {
 6     private static SingletonClass singletonClass = new SingletonClass();
 7     //private constructor
 8     private SingletonClass(){
 9
10     }
11     public static SingletonClass getInstance(){
12         return singletonClass;
13     }
14
15 }

这种方法有一个缺陷,就是中程序没有使用到它的时候,实例已经被创建了。当你创建数据库连接或者socket的时候,这可能成为一个相当打的问题,会导致内存泄漏问题。解决的方法是,在需要的时候

再创建实例,我们称为懒汉式初始化。

懒汉式

与饿汉式相反,你在getInstance()方法中初始化类实例。方法中判断实例是否已经创建,如果已经创建,则返回旧的实例,反之在JVM中创建新的实例并返回。

 1 package com.net;
 2
 3 /**
 4  * Created by admin on 2017/9/28.
 5  */
 6 public class SingletonClass {
 7     private static SingletonClass singletonClass = null;
 8
 9     private  SingletonClass(){
10
11     }
12
13     public  static  SingletonClass getInstance(){
14         if (singletonClass == null){
15             singletonClass = new SingletonClass();
16         }
17         return singletonClass;
18     }
19
20 }

我们都知道这JAVA中,如果两个对象是相同的,那么他们的hashCode也是相同的。我们测试一下,如果上面的单例类都正确,那么他们应该会返回相同的哈希值。

 1 package com.net.test;
 2
 3 import com.net.SingletonClass;
 4
 5 /**
 6  * Created by admin on 2017/9/28.
 7  */
 8 public class Test {
 9     public static void main(String[] args) {
10         SingletonClass singletonClass1 = SingletonClass.getInstance();
11         SingletonClass singletonClass2 = SingletonClass.getInstance();
12         System.out.println("singletonClass1hashcode:"+singletonClass1.hashCode()); 13         System.out.println("singletonClass2hashcode:"+singletonClass1.hashCode()); 14  } 15 }

输出日志:

singletonClass1hashcode:21029277
singletonClass2hashcode:21029277

可以看到两个实例拥有相同的的hashcode。所以,这就意味着上面的代码创建了一个完美的单例类,是吗?回单是no

让单例类反射安全

在上面的单例类中,通过反射可以创建不止一个实例。Java Reflection 是一个在运行时检测或者修改类的运行时行为的过程。通过在运行时修改构造器的可见性并通过构造器创建实例可以产生新的单例实例。

如下代码:

 1 import java.lang.reflect.Constructor;
 2 import java.lang.reflect.InvocationTargetException;
 3
 4 /**
 5  * Created by admin on 2017/9/28.
 6  */
 7 public class Test {
 8     public static void main(String[] args) {
 9         SingletonClass singletonClass1 = SingletonClass.getInstance();
10         SingletonClass singletonClass2 = null;
11
12         try {
13             Class<SingletonClass> clazz = SingletonClass.class;
14             Constructor<SingletonClass> cons = clazz.getDeclaredConstructor();
15             cons.setAccessible(true);
16                 singletonClass2 = cons.newInstance();
17         } catch (Exception e) {
18             e.printStackTrace();
19         }
20         System.out.println("singletonClass1hashcode:"+singletonClass1.hashCode()); 21      System.out.println("singletonClass2hashcode:"+singletonClass2.hashCode()); 22  } 23 }

输入日志:

singletonClass1hashcode:21029277
singletonClass2hashcode:24324022

可以看到每一个实例都有不同的hashCode。显然这个单例类是不合格的。

解决方案:

为了防止反射导致的单例失败,当构造器已经初始化并且其他类再次初始化时,抛出一个运行时异常。下面是更新后的代码:

 1 package com.net;
 2
 3 /**
 4  * Created by admin on 2017/9/28.
 5  */
 6 public class SingletonClass {
 7     private static SingletonClass singletonClass = null;
 8
 9     private  SingletonClass(){
10         if(singletonClass != null){
11             throw new RuntimeException("Use getInstance method to get the single instance of this class");
12         }
13     }
14
15     public  static  SingletonClass getInstance(){
16         if (singletonClass == null){
17             singletonClass = new SingletonClass();
18         }
19         return singletonClass;
20     }
21
22 }

让单例类线程安全

如果两个线程几乎同时去尝试初始化单例类,将会发生什么?测试下面代码

 1 import com.net.SingletonClass;
 2
 3 import java.lang.reflect.Constructor;
 4 import java.lang.reflect.InvocationTargetException;
 5
 6 /**
 7  * Created by admin on 2017/9/28.
 8  */
 9 public class Test {
10     public static void main(String[] args) {
11         Thread thread1 = new Thread(new Runnable() {
12             @Override
13             public void run() {
14                 SingletonClass singletonClass1 = SingletonClass.getInstance();
15                 System.out.println("singletonClass1 hashcode:"+singletonClass1);
16             }
17         });
18         Thread thread2 = new Thread(new Runnable() {
19             @Override
20             public void run() {
21                 SingletonClass singletonClass2 = SingletonClass.getInstance();
22                 System.out.println("singletonClass2 hashcode:"+singletonClass2);
23             }
24         });
25         thread1.start();
26         thread2.start();
27
28     }
29 }

如果你多次运行这些代码,有时候你会发现不同的线程出现了不同的实例。如下:

singletonClass2 hashcode:21029277
singletonClass1 hashcode:24324022

这说明了你的单例类并不是线程安全的。所有的线程同时调用getInstance()方法时,singletonClass==null条件对所有的线程返回值,所以两个不同的实例被创建出来。这就打破了单例的原则。

解决方案

同步getInstance()方法

 1 package com.net;
 2
 3 /**
 4  * Created by admin on 2017/9/28.
 5  */
 6 public class SingletonClass {
 7     private static SingletonClass singletonClass = null;
 8
 9     private  SingletonClass(){
10         if(singletonClass != null){
11             throw new RuntimeException("Use getInstance method to get the single instance of this class");
12         }
13     }
14
15     public synchronized static  SingletonClass getInstance(){
16         if (singletonClass == null){
17             singletonClass = new SingletonClass();
18         }
19         return singletonClass;
20     }
21
22 }

在外面同步getInstance()方法之后,第二个线程必须等到第一个线程执行完getInstance()方法之后才能执行,这就保证了线程安全。

但是,这个方法同样有一些缺点:

锁的开销导致运行变慢

实例变量初始化之后的同步操作是不必要的双检查锁

使用双检查锁 方法创建实例可以克服上面的问题。

这种方法中,当实例为空时,中同步代码块中创建实例,这样只有当singletonClass为空的时候,同步代码块才会执行,避免了不必要的同步操作。

 1 package com.net;
 2
 3 /**
 4  * Created by admin on 2017/9/28.
 5  */
 6 public class SingletonClass {
 7     private static SingletonClass singletonClass = null;
 8
 9     private  SingletonClass(){
10         if(singletonClass != null){
11             throw new RuntimeException("Use getInstance method to get the single instance of this class");
12         }
13     }
14
15     public  static  SingletonClass getInstance(){
16
17         if (singletonClass == null){ //check for the first time
18             synchronized (SingletonClass.class){
19               if(singletonClass ==null){//check for the second time
20               锁 = new SingletonClass();
21               }
22             }
23         }
24         return singletonClass;
25     }
26
27 }

使用volatile关键字

表面上看,这个方法看起来很完美,你只需要付出一次静态代码块的代价。但是除非你使用volatile关键字,否则单例仍然会被打破。

没有volatile修饰符,另一个线程可能在变量 singletonClass正在初始化尚未完成时引用它。但是通过volatile的保证happens-before关系,所有对于singletonClass变量的写操作都会在读操作之前发生。

 1 package com.net;
 2
 3 /**
 4  * Created by admin on 2017/9/28.
 5  */
 6 public class SingletonClass {
 7     private static volatile SingletonClass singletonClass = null;
 8
 9     private  SingletonClass(){
10         if(singletonClass != null){
11             throw new RuntimeException("Use getInstance method to get the single instance of this class");
12         }
13     }
14
15     public  static  SingletonClass getInstance(){
16
17         if (singletonClass == null){ //check for the first time
18             synchronized (SingletonClass.class){
19               if(singletonClass ==null){//check for the second time
20               singletonClass = new SingletonClass();
21               }
22             }
23         }
24         return singletonClass;
25     }
26
27 }

现在上面的单例类是线程安全的。在多数线程应用环境中保证单例类的线程安全是必须的。

让单例类序列化安全

在分布式的系统中,有些情况下你需要做单例类中实现Serralizable接口。这样你可以在文件系统中存储它的状态并且在稍后的某一事件点取出。

让我们测试一个这个单例类中序列化和反序列化之后是否仍然保持单例。

 1 package com.net.test;
 2
 3 import com.net.SingletonClass;
 4
 5 import java.io.*;
 6 import java.lang.reflect.Constructor;
 7 import java.lang.reflect.InvocationTargetException;
 8
 9 /**
10  * Created by admin on 2017/9/28.
11  */
12 public class Test {
13     public static void main(String[] args) {
14         try {
15             SingletonClass singletonClass1 = SingletonClass.getInstance();
16             ObjectOutput out = null;
17             out = new ObjectOutputStream(new FileOutputStream("filename.cc"));
18             out.writeObject(singletonClass1);
19             out.close();
20             //deserialize from file to object
21             ObjectInput in = new ObjectInputStream(new FileInputStream("filename.cc"));
22              SingletonClass singletonClass2 = (SingletonClass)in.readObject();
23              in.close();
24             System.out.println("singletonClass1hashcode:"+singletonClass1.hashCode());
25             System.out.println("singletonClass2hashcode:"+singletonClass2.hashCode());
26         } catch (IOException e) {
27             e.printStackTrace();
28         } catch (ClassNotFoundException e) {
29             e.printStackTrace();
30         }
31
32     }
33 }

输出结果:

singletonClass1hashcode:23050916
singletonClass2hashcode:9286386

显然看到实例的hashcode是不同的,违反了单例原则。序列化单例类后,我们反序列化时,会创建一个新的类实例。为了预防另一个实例的产生,你需要提供readResolve()方法的实现。readResolve()

代替了从流中读取对象。这就确保了中序列化和反序列化的过程中没有人可以创建新的实例。

 1 package com.net;
 2
 3 import java.io.Serializable;
 4
 5 /**
 6  * Created by admin on 2017/9/28.
 7  */
 8 public class SingletonClass implements Serializable{
 9     private static volatile SingletonClass singletonClass = null;
10
11     private  SingletonClass(){
12         if(singletonClass != null){
13             throw new RuntimeException("Use getInstance method to get the single instance of this class");
14         }
15     }
16
17     public  static  SingletonClass getInstance(){
18
19         if (singletonClass == null){ //check for the first time
20             synchronized (SingletonClass.class){
21               if(singletonClass ==null){//check for the second time
22               singletonClass = new SingletonClass();
23               }
24             }
25         }
26         return singletonClass;
27     }
28     //make singleton from serialize and deserialize operation
29     protected Object readResolve(){
30         return getInstance();
31     }
32 }
				
时间: 2024-08-28 14:34:19

如何创建一个完美的单例模式的相关文章

完美的单例模式创建

今天把设计模式拿出来看了下,发现以前对于单例模式的理解很是肤浅,没考虑线程安全,在考虑了线程安全的情况下又没考虑性能,当然我遇到的系统都不怎么考虑并发性能,所以其实也无所谓,当看到单例模式的时候,上网搜索了下,发下一片很好的帖子,我把其中的单例模式整理了下,给了个结果出来. 帖子地址:http://blog.csdn.net/zhangerqing/article/details/8194653 前提都是在考虑线程安全的情况下,可以有两种情况.分别是懒汉式和饿汉式的一种实现. 1.性能不太好,每

在java中写出完美的单例模式

1. 前言 单例(Singleton)应该是开发者们最熟悉的设计模式了,并且好像也是最容易实现的--基本上每个开发者都能够随手写出--但是,真的是这样吗? 作为一个Java开发者,也许你觉得自己对单例模式的了解已经足够多了.我并不想危言耸听说一定还有你不知道的--毕竟我自己的了解也的确有限,但究竟你自己了解的程度到底怎样呢?往下看,我们一起来聊聊看~ 2. 什么是单例? 单例对象的类必须保证只有一个实例存在--这是维基百科上对单例的定义,这也可以作为对意图实现单例模式的代码进行检验的标准. 对单

创建型模式之单例模式

单例模式,英文原话为:Ensure a class has only one instance, and provide a gloabal point of access to it;即:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 单例模式的主要作用是:确保一个类只有一个实例存在.单例模式可以用在建立目录.数据库连接等需要单线程操作的场合,用于实现对系统资源的控制. 单例模式又有两种表现形式:饿汉式单例,类加载时实例化对象:懒汉式单例,第一次引用时实例化对象.两种表现形式

[安卓基础] 005.创建一个简单的UI

*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } a { color: #4183C4; text-decoration: none; } a.absent { color: #cc0000; } a.anchor { display: block; padding-left: 30px; margin-left: -30px; cursor: poin

每天一个设计模式-4 单例模式(Singleton)

每天一个设计模式-4 单例模式(Singleton) 1.实际生活的例子 有一天,你的自行车的某个螺丝钉松了,修车铺离你家比较远,而附近的五金店有卖扳手:因此,你决定去五金店买一个扳手,自己把螺丝钉固定紧.不一会儿,自行车就被你修好了:首先,这个扳手你不会扔掉,下次用的时候直接找出来就用了.好,今天的主题出来了“单例模式”. 2.与变成关联 在上面的例子中,能找出几个关键字:“买一个扳手”,“螺丝钉固定紧”,“不会扔掉扳手”,“下次用直接找出来”.我们结合标题和这几个关键字好好理解一下: 买一个

Python版设计模式: 创建型模式:单例模式和工厂模式家族

一. 单例模式(Singleton) 所谓单例模式,也就是说不管什么时候都要确保只有一个对象实例存在.很多情况下,整个系统中只需要存在一个对象,所有的信息都从这个对象获取,比如系统的配置对象,或者是线程池.这些场景下,就非常适合使用单例模式. 总结起来,就是说不管初始化一个对象多少次,真正干活的对象只会生成一次并且在首次生成. 用Python 实现单例模式的方法有很多,先来看第一种方式. # !/usr/bin/python3 # -*- coding:utf-8 -*- class singl

利用django创建一个投票网站(三)

创建你的第一个 Django 项目, 第三部分 这一篇从第二部分(zh)结尾的地方继续讲起.我们将继续编写投票应用,并且聚焦于如何创建公用界面--也被称为"视图". 设计哲学 Django 中的视图的概念是「一类具有相同功能和模板的网页的集合」.比如,在一个博客应用中,你可能会创建如下几个视图: 博客首页--展示最近的几项内容. 内容"详情"页--详细展示某项内容. 以年为单位的归档页--展示选中的年份里各个月份创建的内容. 以月为单位的归档页--展示选中的月份里各

利用django创建一个投票网站(五)

创建你的第一个 Django 项目, 第五部分 这一篇从第四部分(en)结尾的地方继续讲起.我们在前几章成功的构建了一个在线投票应用,在这一部分里我们将其创建一些自动化测试. 自动化测试简介 自动化测试是什么? 测试,是用来检查代码正确性的一些简单的程序. 测试在不同的层次中都存在.有些测试只关注某个很小的细节(某个模型的某个方法的返回值是否满足预期?),而另一些测试可能检查对莫个软件的一系列操作(某一用户输入序列是否造成了预期的结果?).其实这和我们在教程的第一部分(zh)里做的并没有什么不同

利用django创建一个投票网站(二)

创建你的第一个 Django 项目, 第二部分 这一篇从第一部分(zh)结尾的地方继续讲起.本节我们将继续写 Web 投票应用,并主要关注 Django 提供的自动生成的管理页面(admin site). 设计哲学 为你的员工和客户创建一个用于添加.修改和删除网站内容的管理页面是一项乏味的工作,而且不需要太多的创造力.因为这些原因,Django 提供完全自动地为模型创建管理接口的功能. Django 产生于一个公众页面和内容发布者页面完全分离的新闻类站点的开发过程中.站点管理人员使用管理系统来添