23种设计模式(一)单例模式
一、单例模式
概念:
-
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
-
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
实现单例模式最关键的步骤就是 构造器私有
1.1 饿汉式
/**
* 单例模式是最常见的一种设计模式
* 它有以下特点
* 1、单例类只能有一个实例
* 2、单例类必须自己创建自己的唯一实例
* 3、单例类必须给所有其他对线提供这一实例
*
* 饿汉式单例模式
* 饿汉式的特点就在于
* 在类创建的同时就创建好了一个静态的对象供系统使用
* 以后不会再改变,所以天生就是线程安全的
* 但是这会造成内存的浪费
* @author kimariyb
*/
public class HungryMan {
/**
* 静态、私有的声明一个常量对象
* 并且将构造器私有
* 这样只能通过一个静态的方法来创建这个对象
* 这个对象是一个常量对象
* 这样就达到来单例的目的
*/
private static final HungryMan HUNGRY_MAN = new HungryMan();
private HungryMan() {
}
public static HungryMan getInstance() {
return HUNGRY_MAN;
}
}
饿汉式单例无疑是最简单的,最容易实现的一种模式了
1.2 懒汉式
1.2.1 线程不安全的
? 线程不安全的懒汉式是最基本的实现方式,它最大的问题就在于不支持多线程安全。它在多线程下是不能工作的。
/**
* 懒汉式单例
* 懒汉式的特点就在于
* 它不会造成内存的浪费
* 但是它会带来多线程安全的问题
* @author kimariyb
*/
public class LazyManUnsafe {
private static LazyManUnsafe LazyManUnsafe;
private LazyManUnsafe() {
}
public static LazyManUnsafe getInstance() {
if (LazyManUnsafe == null) {
LazyManUnsafe = new LazyManUnsafe();
}
return LazyManUnsafe;
}
}
1.2.2 线程安全的
? 在原来的代码基础上加上关键字synchronized
才能保证单例,这使得多线程安全得到保障。但是会影响效率。
/**
* 这是一种线程安全的懒汉式
* 它主要的原理就在于
* 加上了锁 synchronized
* 能在多线程中工作,但是这降低了效率
* @author kimariyb
*/
public class LazyManSafe {
private static LazyManSafe lazyManSafe;
private LazyManSafe() {
}
public static synchronized LazyManSafe getInstance() {
if (lazyManSafe == null) {
lazyManSafe = new LazyManSafe();
}
return lazyManSafe;
}
}
1.3 双重检测
? 这是一种能在多线程下运行并且高性能的单例模式
/**
* 双重检测(dcl)
* dcl 模式下最重要的莫过于 volatile 关键字
* 它会解决指令重排的问题
* @author kimariyb
*/
public class DoubleCheck {
private volatile static DoubleCheck doubleCheck;
private DoubleCheck() {
}
public static DoubleCheck getInstance() {
if (doubleCheck == null) {
synchronized (DoubleCheck.class) {
if (doubleCheck == null) {
//这不是原子性操作
//1、分配内存空间
//2、执行构造方法,初始化对象
//3、把这个对象指向这个空间
doubleCheck = new DoubleCheck();
}
}
}
return doubleCheck;
}
}
所谓的原子性操作指的是所处的层(layer)的更高层不能发现其内部实现与结构。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。
1.4 静态内部类
? 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
/**
* 静态内部类
* 在内部类中声明对象
* @author kimariyb
*/
public class StaticHolder {
private StaticHolder() {
}
public static class HolderClass {
private static final StaticHolder STATIC_HOLDER = new StaticHolder();
}
public static StaticHolder getInstance() {
return HolderClass.STATIC_HOLDER;
}
}
1.5 枚举
? 在说明枚举如何实现之前,我们应该思考,为什么枚举能够实现单例模式,这样做的优点在哪里。
1.5.1 使用反射破坏单例
? 在 Java 中反射是一个很强大的功能,毫不夸张的讲,反射是万能的。
public static void main(String[] args) throws Exception {
//使用反射破坏单例
//使用反射得到构造器
Constructor declaredConstructor =
Reflection.class.getDeclaredConstructor(null);
//构造器设置为可访问得
declaredConstructor.setAccessible(true);
Reflection reflection1 = declaredConstructor.newInstance();
Reflection reflection2 = declaredConstructor.newInstance();
//对比哈希值
System.out.println(reflection1.hashCode());
System.out.println(reflection2.hashCode());
}
? 输出结果如下:
根据哈希值,我们很清楚的看见单例模式被破坏了!
1.5.2 使用枚举避免被反射破坏
? 使用枚举来避免反射被破坏的原理,我们可以在 Java 源码中窥见一二。我们找到newInstance()
方法的源码,可以看见一行 "Cannot reflectively create enum objects"。
/**
* 枚举类
* 枚举本身也是一个 class
*
* @author kimariyb
*/
public enum EnumSingle {
/**
* 代替原来的对象
*/
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}