单例模式-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(); } }
本文来自博客园,作者:l-coil,转载请注明原文链接:https://www.cnblogs.com/l-coil/p/12863197.html