概念
程序在运行时,通常都会生成很多实例。例如,表示字符串的 String 类的实例与字符串是一一对应的关系,所以当有 1000 个字符串的时候,会生成 1000 个实例。
但是,当我们想在程序中表示某个东西只会存在一个时,就会有“只能创建-一个实例”的需求。典型的例子有表示程序所运行于的那台计算机的类、表示软件系统相关设置的类,以及表示视窗系统(window system)的类。
当然,只要我们在编写程序时多加注意,确保只调用一次 new MyClass(),就可以达到只生成一个实例的目的。但是,如果我们不想“必须多加注意才能确保生成一个实例”,而是要达到如下目的时,应当怎么做呢?
- 想确保任何情况下都绝对只有 1 个实例
- 想在程序上表现出“只存在一个实例”
像这样的确保只生成-一个实例的模式被称作 Singleton 模式。Singleton 是指只含有一个元素的集合。因为本模式只能生成一个实例,因此以 Singleton 命名。
实现方式
众所周知,一个类的实例对象产生是通过构造函数来完成的. 因此如果不想让外界随意新建对象的话我们可以通过把构造函数私有化. 当然为了让类保证可用, 就需要自己提供一个可以返回自己的实例对象的通道, 一般我们使用静态方法来暴露实例对象.
实现单例模式的方式有很多种,不同的方式都有不同的优缺点,下面我们就来一一讲解:
饿汉式
/**
* 单例模式--饿汉式
* @author yuxuan
*/
public class Hungry {
//内部实例化一个对象
private static Hungry hungry = new Hungry();
/**
* 私有构造
*/
private Hungry() {
}
public static Hungry getInstance() {
return hungry;
}
}
上面就是一个简单的单例实现,一般称之为饿汉式.为何叫饿汉式,这个比喻很形象,可以看出上面的对象其实在类第一次被加载后就被创建了.这样有个好处就是避免了线程安全问题.但是这样另外一个问题,因为对象实在类被第一次加载后就被创建了,可能会造成不必要的消耗,因为有可能这个实力不会被用到.基于这个原因从而引出另一个实现方式: 懒汉式
,下面我们来看看:
懒汉式
/**
* 单例模式--懒汉式
* @author yuxuan
*/
public class Lazy {
//内部实例化一个对象
private static Lazy lazy;
/**
* 私有构造
*/
private Lazy() {
}
//对外提供获取对象方法
public static Lazy getInstance() {
//对象被使用时才初始化
if(lazy == null) {
lazy = new Lazy();
}
return lazy;
}
}
上面的实现方式就叫做懒汉式。懒汉,顾名思义就是不会提前把实例对象创建出来,而是将创建的动作放在了第一次使用的时候.
但是,仔细一看就会发现上面的实现方式存在一个问题,那就是线程安全问题。在多线程情况下,有可能两个线程同时需要使用此对象,从而存在同时进入if语句中,最后在两个线程执行完方法后创建了两个不同的对象. 针对这个问题, 我们尝试做出如下修改:
public class Lazy {
//内部实例化一个对象
private static Lazy lazy;
/**
* 私有构造
*/
private Lazy() {
}
//对外提供获取对象方法
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
}
通过修改之后,我们发现可以通过加锁的方式来解决了线程同步的问题.
但是熟悉Java内存模式的同学会发现还是会存在潜在的危险.
在J2SE1.5版本之前使用双重锁检查时会有潜在的危险,有时会正常工作,有时候会因为线程的调度和其他并发系统活动,不正确的实现双重检查锁导致的异常结果可能会间歇性出现。重现异常是十分困难的。此问题在J2SE 5.0中被处理了.我们可以通过volatile关键字来处理
我们只需要在我们声明的对象上加上volatile
修饰
private static volatile Lazy lazy;
好了,到此关于线程安全的问题我们解决了.
现在我们再来考虑一个问题, 做过java的同学,想必对反射和序列化不陌生.为什么在这里提这两个呢? 因为这两个可能会破坏我们的单例.(具体什么原因,后面我会有详细的文章介绍).针对这个问题我们可以做出如下修改就可以解决:
//我们只需要在单例类里定义此方法就能解决
private Object readResolve() {
return lazy;
}