- 单例模式
- 单例模式定义及应用场景
- 饿汉式单例模式
- 懒汉式单例模式
- 采用静态内部类的方式
- 单例模式破坏
单例模式(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.序列化破坏单例
未完待续。。。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)