Java设计模式之单例模式

单例模式

Singleton Pattern,单例模式。Java中最简单设计模式之一,也是Java程序员接触最多使用最多的模式之一。常见的应用场景,例如Spring中管理的bean实例默认情况下是单例的。

单例模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。有如下特征:

  1. 只能有一个实例。
  2. 必须自己创建自己的唯一实例。
  3. 必须给所有其他对象提供这一实例。

任何设计模式的出现,本质上都为某一个可重复的设计问题提供了一套解决方案。我们使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。单例模式的出现是为了一个全局使用类只有一个实例,并提供一个访问它的全局访问点,避免频繁地创建与销毁,造成不必要的系统资源消耗。其本质是判断程序是否已经有这个单例,如果有则返回,如果没有则创建。

以下是一个单例模式的简单实现:

1.先创建一个对象

public class SingleObjectTest {

    /**
     * 静态对象,作为外部共享的唯一实例
     * (静态成员变量存储在方法区的静态区,在类加载初始化后就可以使用)
     */
    private static SingleObjectTest instance = new SingleObjectTest();

    /**
     * 构造函数私有化,为了防止在外部对其实例化
     */
    private SingleObjectTest() { }

    /**
     * 返回唯一可以的实例
     * @return
     */
    public static SingleObjectTest getInstance() {
        return instance;
    }

    /**
     * 测试方法
     */
    public void test() {
        System.out.println("这是单例模式。");
    }
}

2.获取该类的实例

public class TestMain {

    public static void main(String[] args) {
        //这样new对象,编译错误:SingleObjectTest() has private access in SingleObjectTest
        //SingleObjectTest test = new SingleObjectTest();
        
        /* 获取可用实例 */
        SingleObjectTest singleObjectTest = SingleObjectTest.getInstance();
        /* 使用 */
        singleObjectTest.test();
    }
}
单例模式的实现方式

单例模式分饿汉式单例懒汉式单例,实现的方式有多种。

1.饿汉式单例类

饿汉式单例类是实现起来最简单的单例类。线程安全,但没有延迟加载(Lazy Load)
,是常用的实现方式,但容易产生垃圾对象。

实现代码:

public class SingleObjectTest {
    
    private static SingleObjectTest instance = new SingleObjectTest();
    
    private SingleObjectTest() { }
    
    public static SingleObjectTest getInstance() {
        return instance;
    }
}

这代码和前面的代码没什么区别。当SingleObjectTest类被加载时,静态变量instance会被初始化,此时SingleObjectTest类的私有构造函数会被调用,单例类的唯一实例将被创建。
弊端:类加载时就初始化,浪费内存。为了解决这个问题,于是引出了懒汉式单例类。

2.懒汉式单例类

懒汉式单例类是结合线程锁来实现的单例类,是为了第一次调用才实例化,避免内存浪费。这种方式具备很好的 lazy loading,线程安全。

实现代码:

public class SingleObjectTest {

    /**
     * 避免类加载就实例对象,节省系统资源
     */
    private static SingleObjectTest instance = null;

    private SingleObjectTest() { }

    public static synchronized SingleObjectTest getInstance() {
        //当程序需要该实例时,才创建
        if (instance == null) {
            instance = new SingleObjectTest();
        }
        return instance;
    }
}

上面代码虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会很消耗系统资源。那么,如何既解决线程安全问题又不影响系统性能?懒汉式单例类还有一种实现方式,双检锁/双重校验锁(DCL,即 double-checked locking)。

实现代码:

public class SingleObjectTest {

    private static volatile SingleObjectTest instance = null;

    private SingleObjectTest() { }

    public static SingleObjectTest getInstance() {
        // 第一重判断
        if (instance == null) {
            synchronized (SingleObjectTest.class) {
                //第二重判断
                if (instance == null) {
                    instance = new SingleObjectTest1();
                }
            }
        }
        return instance;
    }
}

为什么要双重校验锁呢?举个例子,假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断。

这样的方式,既实现了线程安全与延迟加载,又能在多线程情况下能保持高性能。在这里,要注意的是, 双重校验锁方式实现的SingleObjectTest 单例类,instance 静态变量需要加关键字volatile 。

然而双重校验锁方式也并非十全十美,要求JDK 1.5及以上版本。volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低。

3.饿汉式与懒汉式比较

饿汉式单例在类加载的时候就实例化,线程安全,可以确保唯一实例。但由于没有实现延迟加载(Lazy Load),假如实例化所需要的时间不短,这将会浪费系统资源。不过从调用速度和反应时间角度来讲,由于类在加载的时候就实例化,这点是优于懒汉式的。

懒汉式则在单例类第一次调用时,才开始实例化,实现了延迟加载(Lazy Load),采用双重校验锁方式保证了线程安全。和饿汉式单例相比,系统资源利用得更加合理更加高效。但由于加锁的原因,在单例类尚没有实例化时,多线程同时进入,系统运行效率可能降低。

对于我们来说,使用哪种方式的单例模式,要结合具体的业务来选择。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值