设计模式——单例模式 Java源代码

四个不同单例模式写法的Java源代码

  • 懒汉模式
  • 饿汉模式
  • 懒汉模式 + 安全线程
  • 双重检验锁

第一个懒汉模式线程不安全,后三个都是线程安全的。四者的共有特点,也是单例模式的最主要特点,是其构造函数是私有的。还有一个特点是,有一个静态的getInstance() 方法,静态方法就是类方法。


懒汉模式

这个写法是GoF提出单例模式时候给出的例子,影响力大,写法简单。

package singleton;

// 懒汉模式
public class SingletonLazy
{
    private static SingletonLazy uniqueInstance;

    private SingletonLazy(){} // 私有的构造函数

    public static SingletonLazy getInstance()
    {
        if(uniqueInstance == null)
        {
            uniqueInstance = new SingletonLazy();
        }
        return uniqueInstance;
    }
    public String toString()
    {
        return "A simple way to apply Singleton, but is not thread safe";
    }
}

然而问题在于:当有多于一个线程的时候,懒汉模式可能失效。举个例子:

  1. 线程A和线程B,相隔0.1纳秒分别执行getInstance()方法,A先于B
  2. 时刻”T 纳秒”, A发现 uniqueInstance==null,准备开始new SingletonLazy( )
  3. 时刻”T+0.1 纳秒”, A还没来得及new完SingletonLazy对象此时B发现 uniqueInstance==null,也准备开始new SingletonLazy( )
  4. 时刻”T+0.5 纳秒”, A成功new完了SingletonLazy对象,uniqueInstance!=null
  5. 时刻”T+0.6 纳秒”, B成功new完了SingletonLazy对象

可以看出A,B两个线程都new了SingletonLazy对象,懒汉模式失效。原因在于:A和B两个线程相隔非常非常短的时间分别执行getInstance(),而且new SingletonLazy对象这个过程需要花费一定的时间。


饿汉模式

饿汉模式写法,是一上来(类加载的时候)就给你实例一个Singleton对象,不管你此时需不需要。回顾一下懒汉模式写法,你一开始不需要Singleton对象,然后程序运行到某一时刻,第一次调用getInstance()方法,才实例一个Singleton对象。饿汉模式的写法,由JVM保证是安全的(虽然内部机制我不懂,我才刚开始学Java),不过简单想一想,Singleton类加载之前,肯定不会有线程new Singleton(),此时Singleton()的构造函数还不存在呢~

可以这么说,饿汉模式解决线程安全问题的方法是:从根子上回避这个问题。想法很好,写法很简单,不过呢要多花费一些空间(牺牲空间,换取时间,这个世界就是很难有两全其美的事情)

package singleton;

// 饿汉模式
public class SingletonEager
{
    private static SingletonEager uniqueInstance = new SingletonEager();  // 在这里 new

    private SingletonEager(){} // 私有的构造函数

    public static SingletonEager getInstance()
    {
        return uniqueInstance;
    }

    public String toString()
    {
        return "Create the unique instance when the class is loaded, which is thread safe";
    }
}

懒汉模式(线程安全)

在懒汉模式的基础上,在getInstance() 方法的声明中,增加关键词synchronized,就可以实现线程安全了。毕竟同步嘛,线程A和B即使时间相隔非常非常短,比如相隔0.1纳秒,那也是分先后呀。就因为A快上0.1纳秒,所以就”捷足先登“了,拿到了锁!B在0.1纳秒后,发现getInstance()方法上了锁,进不去了。

package singleton;

public class SingletonThreadSafe
{
    private static SingletonThreadSafe uniqueInstance;

    private SingletonThreadSafe(){} // 私有的构造函数
                  // 这里同步了
    public static synchronized SingletonThreadSafe getInstance()
    {
        if(uniqueInstance == null)
        {
            uniqueInstance = new SingletonThreadSafe();
        }
        return uniqueInstance;
    }

    public String toString()
    {
        return "The getInstance() method is declared with keyword 'synchronized',"
                + " which is thread safe, but with low performance";
    }
}

不过《Head First Design Pattern》说:Just keep in mind that synchronizing a method can decrease performance by a factor of 100。相差100倍,这对于程序性能的影响是相当的大呀!


双重检验锁

这个是上面的synchronized方法的升级版本。仔细想一想,只有在第一次getInstance()的时候,才需要new singleton对象,对吧?如果不是第一次getInstance(),那就说明singleton对象已经存在了~于是有了下面的优化代码

package singleton;

// double checked locking
public class SingletonDCL
{        // 注意这个关键词
    private volatile static SingletonDCL uniqueInstance;

    private SingletonDCL(){} // 私有的构造函数

    public static SingletonDCL getInstance()
    {
        if(uniqueInstance == null)         // check once
        {
            synchronized(SingletonDCL.class)
            {
                if(uniqueInstance == null) // check twice
                {
                    uniqueInstance = new SingletonDCL();
                }
            }
        }
        return uniqueInstance;
    }

    public String toString()
    {
        return "A thread safe way to apply Singleton with good performance";
    }

}

synchronized的不是一个方法,而是一个方法里面的一个代码块,这样被synchronized的部分减少了。

  • 注意1:synchronized前后分别check两次。第一个check,Singleton是否曾经被实例化过;第二个check,就相当于上一个例子”懒汉模式+线程安全“中的check
  • 注意2:volatile关键词,这涉及到JVM内部的机制,先强行记住就行了。

测试及补充

package singleton;

public class Main
{

    public static void main(String[] args)
    {
        SingletonLazy singletonLazy  = SingletonLazy.getInstance();
        SingletonLazy singletonLazy2 = SingletonLazy.getInstance();

        System.out.println(singletonLazy);

        if(singletonLazy2.equals(singletonLazy))
        {
            System.out.println("true"); // 同一个引用
        }
        else
        {
            System.out.println("false");
        }
    }

}

运行结果:

A simple way to apply Singleton, but is not thread safe
true

补充:
《Head First Design Pattern》书中单例模式就这4种写法。不过其实还有更多写法,在实验楼网站中,就还有静态内部类写法和枚举类型写法。《Head First Design Pattern》在GitHub的代码中,有一个例子为了让单例模式能够派生出子类,把构造函数和静态数据成员声明为protected(子类访问权限)。

深入单例模式以及其他设计模式,猛戳这里(可以在新标签页中打开~)

某大神博客,C++版本的单例模式,我觉得写的不错

这个链接好,C++博大精深。。。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值