浅析单例模式
单例模式概述:
单例模式,我觉得是多种设计模式中最简单、也是应用最多的设计模式。顾名思义,单例模式指的是该类的对象实例为全局唯一变量,也可以说,一个类只有一个实例对象。
具体实现步骤:
1..构造方法私有化 。
2.在类内部创建私有静态对象实例。
3.提供公有获取唯一实例的方法。。
常见的单例模式写法:
饿汉式:
public class singleton {
//构造方法私有化
private singleton(){
}
//创建私有实例对象
private static final singleton instance=new singleton();
//公有方法获取唯一实例
public static singleton getInstance(){
return instance;
}
}
饿汉式,在类加载的时候就会创建唯一的实例对象供外部使用,优点是线程安全。缺点是类加载就创建实例,可能造成内存的浪费。
懒汉式(延迟加载方式):
public class singleton {
private singleton(){
}
//实例化对象先为空
private static singleton instance=null;
//先判断对象是否对象是否为空,如果为空,才开始创建实例化对象。
public static singleton getInstance(){
if(instance==null){
instance=new singleton();
}
return instance;
}
}
懒汉式,采用延迟加载的方式,即类加载的时候并不会创建实例化对象,只有真正用到这个对象,调用getInstance()方法的时候才会去实例化这个对象,所以叫做懒汉式。
懒汉式方法的优点是,并不会浪费系统内存。缺点是并不是线程安全的。
那为了实现线程安全,我们自然会想到使用synchronized同步锁。那到底行不行呢?
懒汉式使用synchronized同步锁:
public class singleton {
//构造方法私有化
private singleton(){
}
//实例化对象先为空
private static singleton instance=null;
//为该方法加锁,保证线程安全。
public static synchronized singleton getInstance(){
if(instance==null){
instance=new singleton();
}
return instance;
}
}
这样做导致的问题是在多线程情况下会导致性能比较低下。那我们可以考虑一下,是不是可以把锁的范围缩小呢?
所以代码可以这样写:
public class singleton {
//构造方法私有化
private singleton(){
}
//实例化对象先为空
private static singleton instance=null;
//为该方法加锁,保证线程安全。
public static singleton getInstance(){
if(instance==null){
synchronized(singleton.class) {
instance = new singleton();
}
}
return instance;
}
}
表面看上去没什么问题,其实仔细考虑我们就会发现可能会出现两个实例化对象的问题。
考虑这样的情况。线程A和线程B同时进入到getInstance()方法里。都判断instance==null,所以都进入下面的if()代码块了。
假设线程A得到cpu使用权,进入到同步代码块,创建了对象并返回对象。当线程A完成以后,此时线程B继续进入同步代码块,就会创建另一个对象并返回。
所以这样的话就会返回不止一个对象实例啦。
我们可以看一下测试代码的结果:
所以为了解决这种问题,我们可以在锁内再判断一次是否对象是否为空。这就是DCL(双重检测机制)懒汉式。
其实这样还不完全稳,会有重排序的问题,这里就不细说,感兴趣的自己了解。要解决重排序也很简单,使用volatile关键字,具有内存屏障的功能。下面是完整的代码:
双重检测机制(DCL)懒汉式:
public class singleton {
//构造方法私有化
private singleton(){
}
//实例化对象先为空
private static volatile singleton instance=null;
//为该方法加锁,保证线程安全。
public static singleton getInstance(){
if(instance==null){
//锁范围缩小
synchronized(singleton.class) {
//再次判断是否为空
if(instance==null) {
instance = new singleton();
}
}
}
return instance;
}
}
另外一种推荐使用的方法就是静态内部类的方法:
静态内部类:
public class singleton {
//构造方法私有化
private singleton(){
}
//私有静态内部类
private static class LazyHolder{
//创建单例对象
private static final singleton instance=new singleton();
}
//获取单例对象
public static final singleton getInstance(){
return LazyHolder.instance;
}
}
该方法的原理是多线程情况下,不管哪个线程第一次调用getInstance方法都会LazyHolder被加载和初始化,而当初始化静态数据时,java是线程安全的。
最后一种实现方式比较简单,使用枚举:
枚举方法:
public enum Singleton {
instance;
public void whateverMethod() {
}
}
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。