二十三种设计模式分类
一、概述
单例(Singleton
)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows
中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
优点
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
应用场景
1、要求生产唯一序列号。
2、WEB
中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O
与数据库的连接等。
二、实现
1. 单例模式结构图
单例模式的主要角色如下。
- 单例类:包含一个实例且能自行创建这个实例的类。
- 访问类:使用单例的类。
PS
:UML
结构图可以参考,例子实现并不根据UML
图来完成,灵活实现即可;
2. 单例模式实现
2.1. 饿汉式
在JVM
加载类阶段就构建对象,适合绝大部分场景,JVM
只加载一次类,保证了线程安全;
public class Singleton01 {
private static Singleton01 instance = new Singleton01();
private Singleton01() {
}
public static Singleton01 getInstance() {
return instance;
}
public static void main(String[] args) {
Singleton01 singleton01 = Singleton01.getInstance();
Singleton01 singleton011 = Singleton01.getInstance();
System.out.println("Compare:" + (singleton01 == singleton011));
}
}
2.2. 懒汉式
线程不安全,在需要时再构建对象,多个线程同时构建对象时,会构建出多个对象;
public class Singleton02 {
private static Singleton02 instance;
private Singleton02(){
}
public static Singleton02 getInstance() {
if (instance == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton02();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Singleton02.getInstance().hashCode());
}).start();
}
}
}
2.3. 双重锁检查
使用synchronized
类锁限制每次只能有一个线程可以构建新对象,两次判断instance
对象是否为null
,第一次判断如果为null
,则构建新对象;第二次判断是否为null
,则是为了防止A线程已经构建完成了对象,避免B
线程获得锁后再次构建对象,确保该类只有一个实例;
public class Singleton03 {
///volatile防止指令重排序
private volatile static Singleton03 instance;
private Singleton03(){
}
public static Singleton03 getInstance() {
if (instance == null) {
synchronized (Singleton03.class) {
if (instance == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton03();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Singleton03.getInstance().hashCode());
}).start();
}
}
}
2.4. 静态内部类
静态内部类模式,JVM
加载静态内部类只会加载一次,加载过程中完成了新对象构建,保证了线程安全,不能解决反序列化问题;
public class Singleton04 {
private Singleton04(){
}
//使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象
private static class SingletonHolder{
private static Singleton04 instance = new Singleton04();
}
public static Singleton04 getInstance() {
return SingletonHolder.instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Singleton04.getInstance().hashCode());
}).start();
}
}
}
2.5. 枚举实现
枚举没有构造函数,所以可解决反序列化的问题,并且保证线程安全;
·public enum Singleton05 {
/**
* 单一实例
*/
INSTANCE;
public static Singleton05 getInstance() {
return Singleton05.INSTANCE;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton05 singleton1 = Singleton05.INSTANCE;
Singleton05 singleton2 = Singleton05.INSTANCE;
System.out.println("正常情况下,实例化两个实例是否相同:" + (singleton1 == singleton2));
Constructor<Singleton05> constructor = null;
constructor = Singleton05.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton05 singleton3 = null;
singleton3 = constructor.newInstance();
System.out.println(singleton1 + "\n" + singleton2 + "\n" + singleton3);
System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:" + (singleton1 == singleton3));
}
}··