设计模式之-单例模式

242 阅读3分钟

1.单例模式应用场景

单例模式(Singleton Pattern)是指确保一个雷=类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。实际开发,在Spring框架应用中的ApplicationContext、数据库的连接池等等都是应用了单例模式。

2.饿汉式单例模式

饿汉式单例模式在类加载的时候就立即进行初始化,并且创建初始化对象。她绝对线程安全,在线程还没出现以前就已经实例化了,不可能存在访问安全问题。

  • 优点:没有任何加锁执、行效率比较高。用户体验比懒汉式单例模式更好。

  • 缺点:类加载的时候就进行初始化,不管用不用都占用着空间,浪费了内存,有点“占着茅坑不拉屎”的意思。

    Spring中IOC容器ApplicationContext本身就是典型饿汉式单例模式。

    接下来看一段代码:

public class HungrySingleton {
    //先静态、后动态
    //先属性、后方法
    //先上后下
    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

还有另外一种写法,利用静态代码块的机制:

public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungryStaticSingleton;
    static {
        hungryStaticSingleton = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){
        
    }
    public static HungryStaticSingleton getInstance(){
        return hungryStaticSingleton;
    }
}

这两种写法都非常简单,也非常好理解,饿汉式单例模式适用于单例对象较少的情况。接下来我们看看性能更优的写法。

3.懒汉式单例模式

​ 懒汉式单例模式的特点是:被外部类调用的时候内部类才会被加载下面看懒汉式单例模式的示例代码.

//懒汉式单例模式只有外部使用的时候才进行实例化
public class LazySimpleSingleton {
    private LazySimpleSingleton() {

    }

    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;

    public static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

然后写一个线程类去测试下

public class ExectortTread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton lazySimpleSingleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + lazySimpleSingleton);
    }
}

写一个测试类去测试下

public class LazySingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new ExectortTread());
        Thread t2 = new Thread(new ExectortTread());
        t1.start();
        t2.start();
        System.out.println("结束");
    }
}

经过多次运行,时候得到的运行结果有时候相同,有时候不同,线程安全隐患依然存在。为了进行优化我们可以在getInstance()上加上synchronized关键字,使这个方法变成同步的。

//懒汉式单例模式只有外部使用的时候才进行实例化
public class LazySimpleSingleton {
    private LazySimpleSingleton() {

    }

    //静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;

    public synchronized static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

用synchronized虽然线程安全问题解决了,但是在线程数量比较多资源有限的情况下,会导致大批线程阻塞,影响程序性能。

下边有一种兼顾线程安全,又能提升性能的方式。双重检查锁的单例模式。

public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazy = null;
    private LazyDoubleCheckSingleton(){
        
    }
    public static LazyDoubleCheckSingleton getInstance(){
        if(lazy==null){
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazy==null){
                    lazy=new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazy;
    }
    
}

但是使用synchronized关键字总归要上锁,对程序性能还是有一定的影响。我们还可以从类初始化的角度去考虑,采用静态内部类的方式。

public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton(){
        if(LazyHolder.LAZY!=null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    public static final LazyInnerClassSingleton getInstance(){
        return LazyHolder.LAZY;
    }

    private static class  LazyHolder{
        public static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();

    }
}