设计模式之单例模式

2020-10-05  本文已影响0人  codejr

1 什么是单例模式

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
——《百度百科》

简单来说单例模式就是指在内存中只会创建且仅创建一次对象的设计模式,当程序中其他地方需要使用到该对象的相同功能时,都会调用创建好的这一个,不会再额外创建实例,这样做的好处就是避免过多的创建相同作用的对象使得内存浪费。

2 单例模式分类

在单例模式中主要分为两类,分别是懒汉式和饿汉式

  1. 懒汉式:在程序调用时才创建实例
  2. 饿汉式:在程序加载时就创建好实例,等待被调用

3 单例模式的实现

3.1 懒汉式

懒汉式是在程序调用时才会创建实例,在程序调用时首先会进行判断,如果已经存在该实例,则直接返回,若不存在该实例则创建并返回,懒汉式流程图如下:


懒汉式.png

代码实现
为避免类被多次实例化,所以将类中的构造函数限定为private,这样就能保证其他程序无法通过new关键字来实例化,达到真正的单例。

package com.lee.singleton;

public class Singleton {
    private static Singleton instence = null;
    private Singleton() {}
    public static Singleton getInstence() {
        if(instence == null)
            instence =  new Singleton();
        return instence;
    }
}

通过以上代码就简单的实现了懒汉式的单例模式,但是在上面的代码中还有不完美的地方:如果当程序为多线程时,那么如果线程A和线程B同时调用了Singleton的getInstence()方法,然后同时进入了if判断,这样的话由于线程不安全就会导致被实例化两个对象。
下面通过两个线程来模拟调用该单例模式程序,然后通过实例的hashCode来判断是只创建了一个对象还是创建了两个对象。
测试多线程调用单例模式

public class SingletonTest {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            public void run() {
                Singleton instence = Singleton.getInstence();
                int hashCode = instence.hashCode();
                System.out.println(Thread.currentThread().getName() + ":" + hashCode);
            }
        };
        thread.start();
        Thread thread2 = new Thread() {
            public void run() {
                Singleton instence = Singleton.getInstence();
                int hashCode = instence.hashCode();
                System.out.println(Thread.currentThread().getName() + ":" + hashCode);
            }
        };
        thread2.start();
    }
}

运行结果如下所示:


运行结果.png

由结果我们可以看到两个线程所创建的对象的hashCode值不一样,那么也就代表如果多线程调用该单例模式则会出现线程安全问题。

3.2 懒汉式优化

如果说到解决线程安全问题,最先想到的肯定就是synchronized方法和synchronized代码块。最简单的就是在getInstence方法上加上synchronized,这样当一个线程进入到该方法时,另外的线程就无法进入,可以确保单例。
使用synchronized

//方法加synchronized锁的方式
public synchronized static Singleton getInstence() {
    if(instence == null)
        instence =  new Singleton();
    return instence;
}

//synchronized代码块的方式
public static Singleton getInstence() {
    synchronized(Singleton.class){
        if(instence == null)
            instence =  new Singleton();
    }
    return instence;
}

但是使用以上方法还是不够完美,那就是当已经有了实例之后每次调用getInstence还是会首先获取到锁,然后再进行判断,这样当高并发的情况下性能就会及其低下,所以如果需要安全并且性能好的话就只能使用以下方法。
最终优化方法

public Singleton getInstence() {
    if(instence == null) {
        synchronized(Singleton.class) {
            if(instence == null) {
                instence = new Singleton(); 
            }   
        }
    }
    return instence;
}

该方法只是在synchronized代码块的方式外层又加了一层if判断,这样的话当已经有实例的情况下就会直接返回实例化并不会进入到if中,也就不会去获取锁。并且在没有实例的情况下如果两个线程都进入到了最外层的if判断,那么在线程A获取到锁并进入第二层if中并实例化对象之后,线程B就不会进入到第二层if,能够确保单例。

3.2 饿汉式

饿汉式是指在程序加载时就创建对象,当需要调用时则直接返回实例,不需要和懒汉式一样进行判断是否实例化,饿汉式流程图如下所示:


饿汉式.png

代码实现

public class Singleton {
    private static final Singleton instence = new Singleton();
    private Singleton() {}
    public Singleton getInstence() {
        return instence;
    }
}

4 总结

  1. 单例模式就是在内存中只会创建且仅创建一次对象的设计模式,因为只创建一次对象,所以构造方法私有化,通过getInstence方法获取对象。
  2. 单例模式分为懒汉式和饿汉式,懒汉式是在调用时创建对象,需要注意线程安全和性能优化,饿汉式是在程序加载时就创建对象,需要时直接调用。
  3. 在开发时如果对于内存的要求特别高,使用懒汉式,在需要时才创建,如果对内存要求不高使用饿汉式,饿汉式简单不易出错,而且没有并发安全和性能问题。
上一篇下一篇

猜你喜欢

热点阅读