【单例模式(上)】Java实现单例模式,应该这么学

【单例模式(上)】Java实现单例模式,应该这么学,第1张

单例模式
  • 单例模式
    • 单例模式定义及应用场景
    • 饿汉式单例模式
    • 懒汉式单例模式
    • 采用静态内部类的方式
    • 单例模式破坏

单例模式

单例模式(Singleton Pattern)是Java中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。它可以确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

单例模式定义及应用场景

单例模式( Singleton Pattern )是指确保一个类在任何情况下绝对只有一个实例,例如,公司CEO,部门经理等。J2EE标准中的ServlerContext,ServletContextConfig等,Spring框架应用中ApplicationContext,数据库的连接池等也是单例形式。

饿汉式单例模式

先来看单例模式的类结构图,如下图示:

饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象,它绝对线程安全,在线程还没出现以前就实例化,不可能存在访问安全问题。
接下来看饿汉式单例的标准代码:

  private static final GuPaoSingleton GU_PAO_SINGLETON = new GuPaoSingleton();

  private GuPaoSingleton() {}

  public static GuPaoSingleton getInstance() {
        return GU_PAO_SINGLETON;
    }

注:加载顺序 先静态,后动态。先属性,后方法,先上后下。

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

private static final GuPaoStaticSingleton GU_PAO_STATIC_SINGLETON;

    static {
        GU_PAO_STATIC_SINGLETON = new GuPaoStaticSingleton();
    }

    public static GuPaoStaticSingleton getInstance(){
        return GU_PAO_STATIC_SINGLETON;
    }

这两种写法都非常简单,也非常好理解,饿汉式模式适用于单例对象较少的情况,这样写可以保证绝对线程安全,执行效率高。但是它的缺点也很明显,就是所有对象类加载的时候就实例化。这样一来,系统中就会有大批量的单例对象存在,那么系统初始化就会导致大量的内存浪费,也就是说,不管对象用与不用都占着空间,浪费了内存。

懒汉式单例模式

为了解决饿汉式单例可能带来的内存浪费问题,于是就出现了懒汉式单例的写法,懒汉式单例的特点是,单例对象要在被使用时才会初始化
接下来看懒式单例的标准代码:

 private LazySimpleSingleton() {}

 private static LazySimpleSingleton instance = null;

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

但这样写会带来一个问题,如果在多线程环境下,就会出现线程安全问题。
编写线程类 : ExecutorThread

public class ExecutorThread implements Runnable {


    @Override
    public void run() {
        LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + instance);

    }
}

客户端测试代码如下:

  public static void main(String[] args) {
        new Thread(new ExecutorThread()).start();
        new Thread(new ExecutorThread()).start();
        System.out.println("End");
    }

测试结果:

上面代码有一定概率出现两种不同的结果,这就意味着上面的单例存在线程安全隐患。
我们通过不断的切换线程,我们发现在线程环境下LazySimpleSingleton 被实例化了两次,我们得到的可能是两个相同的对象,实际上是被后面执行的线程覆盖了。

那么我们如何是的懒汉式单例模式在线程环境下保证安全呢?

来看下面代码,给getInstance()加上synchronized关键字,使这个方法实现同步:

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

通过调试:

线程安全的问题解决了,但是用synchronized加锁时,在线程数量较多的情况下,如果CPU分配压力上升,则会导致大批线程阻塞。从而导致程序性能下降。
接下来我们看双重检查锁的单例模式:

public class LazyDoubleCheckSingleton {
    
    private LazyDoubleCheckSingleton() {}

    private volatile static LazyDoubleCheckSingleton instance = null;

    public static LazyDoubleCheckSingleton getInstance() {
        //检查是否阻塞
        if (null == instance) {
            synchronized (LazyDoubleCheckSingleton.class) {
                //检查是否需要重新创建实例
                if (null == instance) {
                    instance = new LazyDoubleCheckSingleton();
                    //指令重排序 需要新加 volatile 关键字
                }
            }
        }
        return instance;
    }
}


通过调试:当第一个线程调用getInstance()方法时,第二个线程也可以调用,当第一个线程执行到synchronized时会上锁,第二个线程就会变成MONTOR状态, 出现阻塞。此时,阻塞并不是基于整个类的阻塞,而是在getInstance()方法内部的阻塞。

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

采用静态内部类的方式

优点: 兼顾饿汉式单例模式的内存浪费问题和synchronized的性能问题。

public class LazyStaticInnerClassSingleton {
 
    private LazyStaticInnerClassSingleton() { }

    public static final LazyStaticInnerClassSingleton getInstance() {
        return lazyHolder.LAZY_STATIC_INNER_CLASS_SINGLETON;
    }

    private static class lazyHolder {
        private static final LazyStaticInnerClassSingleton LAZY_STATIC_INNER_CLASS_SINGLETON = new LazyStaticInnerClassSingleton();
    }
}

这种方式兼顾饿汉式单例模式的内存浪费问题和synchronized的性能问题。内部类一定是要在方法调用之前初始化,巧妙地避开了线程安全问题。

单例模式破坏

1.反射破坏单例

2.序列化破坏单例

未完待续。。。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/869684.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存