单例模式是Java中常用的设计模式,但是你真正了解他的用法吗?
1、 什么是单例模式
单例模式是指在整个应用的生命周期中只能存在一个实例,采用单例模式主要目的是:
- A) 保证实例的唯一性
- B) 减少创建实例的系统开销,避免资源浪费。
2、 为什么用单例模式,而不用静态类?
首先,我们先了解一下静态类是什么:静态类就是一个类里面都是静态方法和静态field,构造器被private修饰,因此不能被实例化,Math类就是一个静态类。静态类中的方法、成员在可以快速通过类名访问,不需要实例化(自然也不存在多个实例)。静态类的优点是:速度更快,因为静态类的绑定是在编译时进行的。但是和单例模式相比,静态类的缺点有:
1) 静态类不能实现面向对象的全部特性(例如:override)
2) 静态类无法实现懒加载,因为成员全部是静态的。
所以,如果你的类不考虑面向对象的能力,可以采用静态类,性能会优于单例类。
3、 单例模式的实现方法
我们来看一下单例类大家常见的几种写法:
第一种方法:
package com.gobixiu.demo;
public class SingletonDemo1 {
private static SingletonDemo1 instance = new SingletonDemo1();
private SingletonDemo1() {
}
public static SingletonDemo1 getInstance() {
return instance;
}
}
这种写法很多人称为“饿汉模式”,在类加载时产生实例,这种模式的缺点是:资源浪费,因为在类加载时就已经生成了实例,如果一个系统中只有1-2个简单的单例类可以不用在乎这种浪费,但如果一个系统中大量业务类需要采用单例时,而且其中有些类可能基本不用,这种浪费也是比较可观的。当然这种模式的优点是:代码简单、不用考虑实例过程中的线程同步问题。
第二种方法:
package com.gobixiu.demo;
public class SingletonDemo2 {
private static SingletonDemo2 instance = null;
private SingletonDemo2() {
}
public static SingletonDemo2 getInstance() {
if(instance==null)
{
instance=new SingletonDemo2();
}
return instance;
}
}
这种方法实现了延时加载,只有在用到的时候才实例化对象,这种方法在单线程环境下没有问题,但是在多线程环境下会出现问题,当多个线程同时调用getInstance时,可能实例化多个对象,因为多个线程同时执行if(instance==null)时,都会得到null的结果,所以new SingletonDemo2()的代码被执行了多次。
第三种方法:
因为第二种写法线程同步的问题,很多人可能会想,那我在方法上加一个synchronized不就可以解决了吗?例如:
package com.gobixiu.demo;
public class SingletonDemo3 {
private static SingletonDemo3 instance = null;
private SingletonDemo3() {
}
public static synchronized SingletonDemo3 getInstance() {
if(instance==null)
{
instance=new SingletonDemo3();
}
return instance;
}
}
确实,这种方法可以防止出现多个实例的问题。但是,这样做的缺点是:效率太低,同时只能有一个线程可以调用getInstance方法。
第四种方法:
基于第三种方法,我们采用双重同步检测,代码如下:
package com.gobixiu.demo;
public class SingletonDemo4 {
private static SingletonDemo4 instance = null;
private SingletonDemo4() {
}
public static synchronized SingletonDemo4 getInstance() {
if(instance==null)
{
synchronized(SingletonDemo4.class)
{
if(instance==null)
{
instance=new SingletonDemo4();
}
}
}
return instance;
}
}
这样看似一个完美的解决方案,既实保证了性能,又考虑到了线程安全的问题。但是Java内存模型中,允许“无序写入”以及允许指令乱序执行(out-of-order),new SingletonDemo4() 语句在编译之后会拆分为多条汇编指令。在 SingletonDemo4 构造函数执行完毕之前,变量 instance 可能成为非 null (可能指向一块刚分配好,即将使用的内存),即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还未初始化的对象,这样会导致系统崩溃(这种错误,一般很难找到原因)。所以,在JDK1.5之后JDK对volatile进行了优化,在变量上加上volatile关键字,可以保证变量的线程可见性和有序性(这里不做深入探讨)。所以正确的写法应该是:
package com.gobixiu.demo;
public class SingletonDemo4 {
private volatile static SingletonDemo4 instance = null;
private SingletonDemo4() {
}
public static synchronized SingletonDemo4 getInstance() {
if(instance==null)
{
synchronized(SingletonDemo4.class)
{
if(instance==null)
{
instance=new SingletonDemo4();
}
}
}
return instance;
}
}