Java Singleton 单例模式思考

Java Singleton 单例模式

在设计模式中,单例模式(Singleton)是最长见得一种设计模式之一。什么是单例模式呢?就是在整个系统中,只有一个唯一存在的实例。这样的情况可以干什么用呢?比如可以统计网站的访问量,一些连接池(数据库连接池等)。

一个最简单的单例模式 – 饿汉模式

那么怎么能保证只有一个对象的存在呢?首先得有一个static的实例,这个方法保证了一个class只有一个实例。还得防止外界使用构造器来new一个实例。

1
2
3
4
5
//一个没有封装的单例模式
public class Singleton {
     public static final Singleton singleton =  new Singleton();
     private Singleton(){}
}

外界就可以使用Singleton.singleton这样的方法来调用了。但是这样存在的问题就是分装不够好,添加一个方法,返回singleton的引用。如下

1
2
3
4
5
6
//最简单的封装单例改进版。饿汉模式
public class Singleton {
     public static final Singleton singleton =  new Singleton();
     private Singleton(){}
     public static Singleton getInstance(){  return singleton; }
}

当代码写到这,我们终于可以松一口气了,原来单例模式也很简单呀,就这么几行的代码。对,其实这个是最简单的一种方式,能够应付大部分的场景的。

不过,其他class在引用Singleton而不使用的时候,虚拟机会自动加载这个类,并且实例化这个对象(这点知道Java虚拟机的类加载就会了解那么一些)。于是我们就有了下面的写法。

延迟实例化(懒汉模式) – 在调用时进行实例化

经常能看见其他的单例模式会教下面的代码,这样的人估计是从《设计模式 – 可复用面向对象软件的基础》那本书看来的。这本书使用的是C++语言写的,然后就将其转到了Java平台来。

首先他们会说我们的代码应该先这样,在调用的时候,发现为null,再进行实例化该类。

01
02
03
04
05
06
07
08
09
10
public class Singleton {
     public static final Singleton singleton =  null ;
     private Singleton(){}
     public static Singleton getInstance(){
         if (singleton ==  null ){  //如果singleton为空,表明未实例化
            singleton =  new Singleton();
         }
         return singleton;
     }
}

然后他们还会说,这样在多线程的情况下会出现这样的情况:两个进程都进入到if (singleton == null),于是两个线程都对这个进行实例化,这样就出问题啦。

所以他们又说应该使用synchronize关键字,在实例化之前,进行加锁行为。于是又产生了一下的代码

01
02
03
04
05
06
07
08
09
10
11
12
13
public class Singleton {
     public static final Singleton singleton =  null ;
     private Singleton(){}
     public static Singleton getInstance(){
         if (singleton ==  null ){  //如果singleton为空,表明未实例化
            synchronize (Singleton. class ){
                if ( singleton ==  null ) {  // double check 进来判断后再实例化。
                    singleton =  new Singleton();
                }
         }
         return singleton;
     }
}

他们就会说,他们的这个代码使用了double check (双重检测),以防止这样的情况:当两个线程执行完第一个singleton == null 后等待锁, 其中一个线程获得锁并进入synchronize后,实例化了,然后退出释放锁,另外一个线程获得锁,进入又想实例化,会判断是否进行实例化了,如果存在,就不进行实例化了。

他们会说,这样的代码perfect,很完美,既解决了多线程带来的问题,又解决了延迟实例化的方式。

我觉得这样的代码只是将C++版的单例模式复制到Java平台,没有Java的特色。第一个就是一个Java特色的代码,它解决了多线程的问题,因为JLS(Java Language Specification)中规定了一个类(Singleton.class)只会被初始化一次,但是不能解决延迟实例化的情况。如需要延迟实例化,可以看下面的方法,使用内部类来实现。

使用内部类的单例模式 (懒汉模式)

刚才说了,第一个不能解决延迟实例化Singleton对象的问题。所以我们使用内部类来进行,看看代码。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
//一个延迟实例化的内部类的单例模式
public final class Singleton {
 
     //一个内部类的容器,调用getInstance时,JVM加载这个类
     private static final class SingletonHolder {
         static final Singleton singleton =   new Singleton();
     }
 
     private Singleton() {}
 
     public static Singleton getInstance() {
         return SingletonHolder.singleton;
     }
  }

我们来看看这个代码。首先,其他类在引用这个Singleton的类时,只是新建了一个引用,并没有开辟一个的堆空间存放(对象所在的内存空间)。接着,当使用Singleton.getInstance()方法后,Java虚拟机(JVM)会加载SingletonHolder.class(JLS规定每个class对象只能被初始化一次),并实例化一个Singleton对象。

这样做就可以解决前面说的对线程多次实例化对象延迟实例化对象的问题了。

缺点:不过你会使用这样复杂的方式嘛?代码那么多。只是为了延迟一个对象的实例化,引入另外一个class。就为了延迟那么一次对象延迟的实例化,延缓Java的heap堆内存。为此付出的代价是引入一个class,需要在Java的另外一个内存空间(Java PermGen 永久代内存,这块内存是虚拟机加载class文件存放的位置)占用一个大块的空间。

还存在的一些问题

好了,在许多情况其实用第一种方法就差不多可以了。在特殊情况下,还是存在着一些问题。

用反射生成对象

如果使用Java的反射机制来生成对象的话,那么单例模式就会被破坏。

1
2
3
4
5
//使用反射破坏单例模式
Class c = Class.forName(Singleton. class .getName());
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible( true );
Singleton singleton = (Singleton)ct.newInstance();

对于用反射破坏单例模式的,是不对其进行代码保护的,即由此造成的后果,由写反射的构建单例实例的人负责。所以我们就不用担心反射带来的问题了。

分布式上,解决单例模式

对于分布式上的单例模式,应该使用RMI(Remote Method Invocation 远程方法调用)来进行。或者使用web serivce,在单个服务器上存在单例,其他的机器使用SOAP协议进行访问。

不同的CLASSLOADER(类加载器)加载SINGLETON

在不同的ClassLoader加载Singleton,他们是不一样的。就像在不同的package中相同的类名,他们是不同的类。同样的,在不同的ClassLoader上加载的类,他们尽管代码一样,还是属于不同的类。

这样,需要自己写classloader,保证Singleton.class的加载唯一。

参考文章:http://www.oschina.net/question/9709_102019

单例模式的对象是否会被JVM回收?

对于这个问题,在还没实例化单例的时候,对象不存在,单实例化后,那么singleton的引用就存在的,只要Singleton.class存在虚拟机中。那么什么时候Singleton.class会被回收呢?对于这个问题,牵扯了许多的问题。因为Singleton对象存在,所以Singleton.class就也存在,这样形成了相互依赖,所以不会被JVM垃圾回收。

网上有个文章验证了他的想法 http://blog.csdn.net/zhengzhb/article/details/7331354

使用反序列化生成对象

如果你的Singleton序列化了,那么通过反序列化方式可以生成一个对象。通过增加readResolve方法来解决。如下

1
2
3
4
5
6
7
//最简单的封装单例改进版。饿汉模式。序列化及反序列化解决
public class Singleton  implements java.io.Serializable {
     public static final Singleton singleton =  new Singleton();
     private Singleton(){}
     public static Singleton getInstance(){  return singleton; }
     private Object readResolve() {   return singleton;   }
}

使用CLONE()克隆对象

Object的clone()方法默认是抛出CloneNotSupportedException异常的,所以只要不覆盖该方法,调用的时候,也会抛出异常。

effective java作者提到用enum枚举来实现单例模式。

扩展阅读:使用enum来进行Java的单例模式:http://coolxing.iteye.com/blog/1446648

转载地址:http://www.chenyudong.com/archives/java-singleton.html

本文详细介绍了如何利用Python语言结合MySQL数据库开发一个学生管理系统。通过这一过程,读者不仅能够掌握系统设计的基本思路,还能学习到如何使用Python进行数据库操作。该系统涵盖了用户界面设计、数据验证以及数据库的增删改查等多个关键环节。 Python作为一种高级编程语言,以简洁易懂著称,广泛应用于数据分析、机器学习和网络爬虫等领域,同时也非常适合用于快速开发数据库管理应用。MySQL是一个广泛使用的开源关系型数据库管理系统,具有轻量级、高性能、高可靠性和良好的编程语言兼容性等特点,是数据存储的理想选择。在本系统中,通过Python的pymysql库实现了与MySQL数据库的交互。 pymysql是一个Python第三方库,它允许程序通过类似DB-API接口连接MySQL数据库,执行SQL语句并获取结果。在系统中,通过pymysql建立数据库连接,执行SQL语句完成数据的增删改查操作,并对结果进行处理。 系统采用命令行界面供用户操作。程序开始时,提示用户输入学生信息,如学号、姓名和各科成绩,并设计了输入验证逻辑,确保数据符合预期格式,例如学号为1至3位整数,成绩为0至100分的整数。 数据库设计方面,系统使用名为“test”的数据库和“StuSys”表,表中存储学生的学号、姓名、各科成绩及总成绩等信息。通过pymysql的cursor对象执行SQL语句,实现数据的增删改查操作。在构建SQL语句时,采用参数化查询以降低SQL注入风险。 系统在接收用户输入时进行了严格验证,包括正则表达式匹配和数字范围检查等,确保数据的准确性和安全性。同时,提供了错误处理机制,如输入不符合要求时提示用户重新输入,数据库操作出错时给出相应提示。 在数据库操作流程中,用户可以通过命令行添加学生信息或删除记录。添加时会检查学号是否重复以避免数据冲突,删除时需用户确认。通过上述分析,本文展示了从
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值