打赏
Fork me on GitHub

单例模式-1(懒汉式、饿汉式)

单例模式的应用场景

  单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也十分广泛。

恶汉单例模式

  恶汉单例模式狮子啊类加载的时候就立即初始化,并且创建单例对象,它绝对的线程安全,在线程还没有出现以前就实例化了,不肯能存才访问安全的问题

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

  缺点:类加载的时候就初始化,不管用与不用都占用空间,浪费了内存,可能“占着茅坑不拉屎”。

Spring 中Ioc 容器ApplicationContentext 本身就是典型的饿汉式单例模式。看下代码。

public class HungrySingleton {
    private static final HungrySingleton hungrySingletion = new HungrySingleton();

    private HungrySingleton(){}

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

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

/**
 * 饿汉式静态块单例
 */
public class HungryStaticSingleton {
    private static final HungryStaticSingleton hungrySingleton;
    static {
        hungrySingleton = new HungryStaticSingleton();
    }
    private HungryStaticSingleton(){}

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

饿汉式单例适用于单例对象较少的情况。

懒汉式单例模式

  懒汉式单例模式的特点是:被外部调用的时候内部类才会被加载。

/**
 * 懒汉式单例模式在外部需要使用的时候才进行实例化
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    //静态代码块,公共内存区域
    private static LazySimpleSingleton lazy = null;
    public static LazySimpleSingleton getInstance(){
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

写一个线程类

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

测试代码如下

public class Tests {
    @Test
    void lazySimpleSingetonTest (){
        Thread t1 = new Thread( new ExcetorThread());
        Thread t2 = new Thread( new ExcetorThread());
        t1.start();
        t2.start();
        System.out.println("End");
    }
}

运行结果如下

  上面的代码有一定概率出现两种不同结果,这意味着上面的单例存才线程安全隐患。经过调试发现在线程环境下LazySimpleSiingleton被实例化了两次。有时候我们看到的运行结果可能是相同的两个对象,实际上是被后面执行的线程覆盖了,我们看到了一个假象,线程安全隐患依旧存在。

  给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监视锁的运行装填,线程安全的问题解决了。但是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;
    }
}

  此时当第一个线程调用 getInstance() 方法时,第二个线程也可以调用。当第一个线程执行到 synchronized 时会上锁,第二个线程就会变成 MONITOR 状态,出现阻塞。此时阻塞并不是基于整个 LazySimpleSingleton 类的阻塞,而是在 getInstance() 方法内部阻塞,对于调用者来说只要逻辑不太复杂,是无感知的。

  但是用到 synchronized 总归要上锁,对于性能还是有一定的影响的。

  可以采用内部类的方式:

public class LazyInnerClassSingleton {
    /**
     * 使用 LazyInnerClassSingleton 的时候,会默认先初始化内部类
     * 如果没有使用,则内部类是不加载的
     */
    private LazyInnerClassSingleton(){}
    public static final LazyInnerClassSingleton getInstance(){
        //再返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }
    //默认不加载
    public static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}
这种形式兼顾了饿汉式单例 的内存浪费问题和 synchronized 的性能问题,完美的屏蔽了这两个缺点。内部类一定是要在方法调用之前初始化,巧妙的避免了线程安全问题。

 反射破坏单例

  上面的单例模式的构造方法除了加上 private 关键字没有任何处理。如果欧文们使用反射调用其构造方法,再调用getInstance() 方法,应该会有两个不用的实例。

测试代码如下:

  

    @Test
    void LazyInnerClassSingletonTest(){
        try {
            Class<?> clazz = LazyInnerClassSingleton.class;
            Constructor c = null;
            c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true);
            Object o1 = c.newInstance();
            Object o2 = c.newInstance();
            System.out.println(o1 == o2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

运行结果如下

显然创建了两个不同的实例。现在在其构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常

public class LazyInnerClassSingleton {
    /**
     * 使用 LazyInnerClassSingleton 的时候,会默认先初始化内部类
     * 如果没有使用,则内部类是不加载的
     */
    private LazyInnerClassSingleton(){
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }
    public static final LazyInnerClassSingleton getInstance(){
        //再返回结果以前,一定会先加载内部类
        return LazyHolder.LAZY;
    }
    //默认不加载
    public static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

 

posted @ 2020-05-10 15:17  l-coil  阅读(352)  评论(0编辑  收藏  举报