java设计模式之单例模式详解

        Java中单例(Singleton)模式是一种广泛使用的设计模式。单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。一些管理器和控制器常被设计成单例模式。

单例模式有以下特点:

    (1)单例类只能有一个实例。

    (2)单例类必须自己创建自己的唯一实例。

    (3)单例类必须给所有其他对象提供这一实例。

单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。

1.饿汉模式

/** 
 * 方法一
 * 单例模式的实现:饿汉式,线程安全 但效率比较低 
 */  
public class SingletonTest {  

    // 定义一个私有的构造方法
    private SingletonTest() {  
    }  

    // 将自身的实例对象设置为一个属性,并加上Static和final修饰符
    private static final SingletonTest instance = new SingletonTest();  

    // 静态方法返回该类的实例
    public static SingletonTest getInstancei() {  
        return instance;  
    }  
  
}
优点是:写起来比较简单,而且不存在多线程同步问题,避免了synchronized所造成的性能问题;
缺点是:当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。

2.懒汉模式

/**  
 *方法二
 * 单例模式的实现:懒汉式,非线程安全   
 *   
 */  
public class SingletonTest {

    // 定义私有构造方法(防止通过 new SingletonTest()去实例化)
    private SingletonTest() {   
    }   

    // 定义一个SingletonTest类型的变量(不初始化,注意这里没有使用final关键字)
    private static SingletonTest instance;   

    // 定义一个静态的方法(调用时再初始化SingletonTest,但是多线程访问时,可能造成重复初始化问题)
    public static SingletonTest getInstance() {   
        if (instance == null)   
            instance = new SingletonTest();   
        return instance;   
    }   
}
优点是:写起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存;

缺点是:并发环境下很可能出现多个SingletonTest实例。

3. 线程安全的懒汉模式

/**  
 *方法三
 * 单例模式的实现:饱汉式,线程安全简单实现   
 *   
 */  
public class SingletonTest {

    // 定义私有构造方法(防止通过 new SingletonTest()去实例化)
    private SingletonTest() {   
    }   

    // 定义一个SingletonTest类型的变量(不初始化,注意这里没有使用final关键字)
    private static SingletonTest instance;   

    // 定义一个静态的方法(调用时再初始化SingletonTest,使用synchronized 避免多线程访问时,可能造成重的复初始化问题)
    public static synchronized  SingletonTest getInstance() {   
        if (instance == null)   
            instance = new SingletonTest();   
        return instance;   
    }   
}
优点是:使用synchronized关键字避免多线程访问时,出现多个SingletonTest实例。

缺点是:同步方法频繁调用时,效率略低。

4.双重校验锁

/**  
 * 方法四
 * 双重校验锁
 * 线程安全  并且效率高  
 *  
 */  
public class SingletonTest { 

    // 定义一个私有构造方法
    private SingletonTest() { 
     
    }   
    //定义一个静态私有变量
    private static SingletonTest instance;  

    //定义一个共有的静态方法,返回该类型实例
    public static SingletonTest getIstance() { 
        // 对象实例化时与否判断(不使用同步代码块,instance不等于null时,直接返回对象,提高运行效率)
        if (instance == null) {
            //同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建)
            synchronized (SingletonTest.class) {
                //未初始化,则初始instance变量
                if (instance == null) {
                    instance = new SingletonTest();   
                }   
            }   
        }   
        return instance;   
    }   
}
我们看到双重校验锁即实现了延迟加载,又解决了线程并发问题,同时还解决了执行效率问题,视乎已经完美了,但是Java中的指令重排优化的存在可能导致双重校验锁失效的风险。所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。

       这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。不过还好在JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题。

优化代码:

/**  
 * 单例模式最优方案
 * 线程安全  并且效率高  
 *  
 */  
public class SingletonTest { 

    // 定义一个私有构造方法
    private SingletonTest() { 
     
    }   
    //定义一个静态私有变量(不初始化,不使用final关键字,使用volatile保证了多线程访问时instance变量的可见性,避免了instance初始化时其他变量属性还没赋值完时,被另外线程调用)
    private static volatile SingletonTest instance;  

    //定义一个共有的静态方法,返回该类型实例
    public static SingletonTest getIstance() { 
        // 对象实例化时与否判断(不使用同步代码块,instance不等于null时,直接返回对象,提高运行效率)
        if (instance == null) {
            //同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建)
            synchronized (SingletonTest.class) {
                //未初始化,则初始instance变量
                if (instance == null) {
                    instance = new SingletonTest();   
                }   
            }   
        }   
        return instance;   
    }   
}

5.枚举单例

        Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定,使得枚举天生保证序列化单例。

class Resource{
}

public enum SomeThing {
    INSTANCE;
    private Resource instance;
    SomeThing() {
        instance = new Resource();
    }
    public Resource getInstance() {
        return instance;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值