单例模式(三)
过气的,终究是过气了
上一章简单介绍了 UML 类图(二), 如果没有看过,请观看上一章
一. 单例模式
所谓的单例设计模式,就是采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,
并且该类只提供一个取得其对象实例的方法 (静态方法)
一.一 单例模式介绍
引用 菜鸟教程里面的单例模式介绍: https://www.runoob.com/design-pattern/singleton-pattern.html
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,
它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,
可以直接访问,不需要实例化该类的对象。
注意:
一.二 介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例:
优点:
缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class)
防止多线程同时进入造成 instance 被多次实例化。
二. 单例模式代码应用
二.一 八种模式
- 饿汉式 (静态常量)
- 饿汉式 (静态代码块)
- 懒汉式 (线程不安全)
- 懒汉式 (线程安全, 同步方法)
- 懒汉式 (线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
二.二 饿汉式 (静态常量)
直接构建对象
二.二.一 代码
public class Single01 {/**类的内部创建对象final static*/private final static Single01 instance = new Single01();/**构建方法地私有化*/private Single01() {}/**对外提供一个静态的公共方法*/public static Single01 getInstance() {return instance;}
}
测试方法:
@Testpublic void oneTest() {Single01 single01 = Single01.getInstance();Single01 single02 = Single01.getInstance();log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
二.二.二 优缺点
优点: 写法比较简单,就是在类装载的时候就完成实例化。 避免了线程同步的问题。
缺点: 在类装载的时候就完成实例化,没有达到 Lazy Loading 懒加载的效果。
如果从始至终从未使用过这个变量,则会造成内存的浪费。
这种方式 基于 classloader 机制避免了多线程同步的问题,不过 instance 在类装载时就进行实例化,
在单例模式中大多数都是调用 getInstance() 方法,但是导致类装载的原因有多种,因此不能确定有其他的方式 (其他的静态方法)
导致类装载, 这时候初始化 instance 就没有达到 lazy loading 的效果。
结论: 这种单例模式可用,但可能会造成内存浪费。
二.三 饿汉式 (静态代码块)
静态代码块里面构建对象
二.三.一 代码
public class Single02 {private static Single02 instance ;static {instance = new Single02();}private Single02 (){}public static Single02 getInstance() {return instance;}
}
@Testpublic void twoTest() {Single02 single01 = Single02.getInstance();Single02 single02 = Single02.getInstance();log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
二.三.二 优缺点
- 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,
也是在类装载的时候,就执 行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。 - 结论:这种单例模式可用,但是可能造成内存浪费
二.四 懒汉式 (线程不安全)
方法中 验证 为空 再进行构建对象
二.四.一 代码
public class LanSingle03 {private static LanSingle03 instance;private LanSingle03 (){}public static LanSingle03 getInstance() {if (instance == null) {instance = new LanSingle03();}return instance;}
}
测试方法:
@Testpublic void threeTest() {LanSingle03 single01 = LanSingle03.getInstance();LanSingle03 single02 = LanSingle03.getInstance();log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
但是该方式在多线程环境下会存在并发问题
@Testpublic void threadTest() throws Exception{for( int i = 0; i< 20; i++) {new Thread(()->{log.info(">>> 打印实例: {}", LanSingle03.getInstance());},i+"").start();}TimeUnit.SECONDS.sleep(2);}
二.四.二 优缺点说明
- 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
- 如果在多线程下,一个线程进行了 if 判断语句,还没有来得及往下执行,另一个线程也通过了这个判断语句, 这时便会产生多个实例。
所以在多线程环境下不可以使用这种方式
结论: 在实际开发中,不要使用这种方式
二.五 懒汉式 (线程安全,同步方法)
方法上添加 synchronized 进行同步
二.五.一 代码
public class LanSingle04 {private static LanSingle04 instance;private LanSingle04(){}public synchronized static LanSingle04 getInstance() {if (instance == null) {instance = new LanSingle04();}return instance;}
}
@Testpublic void fourTest() {LanSingle04 single01 = LanSingle04.getInstance();LanSingle04 single02 = LanSingle04.getInstance();log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
二.五.二 优缺点
- 解决了线程安全问题
- 效率太低了, 每个线程在想获得类的实例的时候,执行 getInstance() 方法都要进行同步。
而其实这个方法只执行一次实例化代码就够了, 后面的想获得该类的实例的时候,直接 return 就行了。
方法进行同步,效率太低。 - 结论: 在实际开发中,不推荐使用这种方式
二.六 懒汉式 (线程安全,同步代码块)
方法中,为空时, 同步类,同步代码块内部进行实例化
二.六.一 代码
public class LanSingle05 {private static LanSingle05 instance;private LanSingle05(){}public static LanSingle05 getInstance() {if (instance == null) {synchronized (LanSingle05.class){instance = new LanSingle05();}}return instance;}
}
@Testpublic void fiveTest() {LanSingle05 single01 = LanSingle05.getInstance();LanSingle05 single02 = LanSingle05.getInstance();log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
有线程安全的问题:
@Testpublic void threadTest() throws Exception{for( int i = 0; i< 20; i++) {new Thread(()->{log.info(">>> 打印实例: {}", LanSingle05.getInstance());},i+"").start();}TimeUnit.SECONDS.sleep(2);}
二.六.二 优缺点
- 有线程同步问题
结论: 在实际开发中,不推荐使用这种方式
二.七 双重检查
双重检查,在 同步代码块内部,再判断一下是否为空, 为空才进行实例化
二.七.一 代码
public class CheckSingle06 {private static CheckSingle06 instance;private CheckSingle06(){}public static CheckSingle06 getInstance() {if (instance == null) {synchronized (CheckSingle06.class){if (instance == null) {instance = new CheckSingle06();}}}return instance;}
}
@Testpublic void sexTest() {CheckSingle06 single01 = CheckSingle06.getInstance();CheckSingle06 single02 = CheckSingle06.getInstance();log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
二.七.二 优缺点
- Double-Check 概念是多线程开发中常使用到的, 进行了两次 if( instance == null) 检查,这样就可以保证线程安全了。
- 实例化代码只用了一次,后面再访问时, 判断 if (instance ==null) 直接 return 实例化对象 ,也避免了反复进行方法同步。
- 线程安全的: 会延迟加载, 效率较高。
- 结论: 在实际开发中, 推荐使用这种单例设计模式
二.八 静态内部类
定义一个静态的内部类, 内部类中属性进行构建
二.八.一 代码
public class InnerSingle07 {private InnerSingle07(){}private static class InnerClass {private static final InnerSingle07 INSTANCE = new InnerSingle07();}public static InnerSingle07 getInstance() {return InnerClass.INSTANCE;}
}
测试方法:
@Testpublic void sevenTest() {InnerSingle07 single01 = InnerSingle07.getInstance();InnerSingle07 single02 = InnerSingle07.getInstance();log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
二.八.二 优缺点
- 采用了类装载的机制来保证初始化实例只有一个线程
- 静态内部类方式在 InnerSingle07 类被装载时并不会立即实例化, 而是在需要实例化时,调用 getInstance 方法,
才会装载 InnerClass 类,从而完成 InnerSingle07 的实例化。 - 类的静态属性只会在第一次加载类的时候初始化, 所以在这里, JVM 帮助我们保证了线程的安全性,
在类进行初始化时,别的线程是无法进入的。 - 优点: 避免了线程不安全,利用静态内部类特点实现了延迟加载,效率高。
- 结论: 推荐使用
二.九 枚举
枚举 enum
二.九.一 代码
public enum EnumSingle08 {INSTANCE("1");private String name;EnumSingle08(String name) {this.name = name;}public String getName() {return name;}
}
@Testpublic void nightTest() {EnumSingle08 single01 = EnumSingle08.INSTANCE;EnumSingle08 single02 = EnumSingle08.INSTANCE;log.info("是否相同: {}", single01 == single02);log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());}
二.九.二 优缺点
- 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建 新的对象。
- 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
- 结论:推荐使用
三. Java 设计模式应用
java.lang.Runtime 类
是饿汉式第一种
本章节的代码放置在 github 上:
https://github.com/yuejianli/DesignPattern/tree/develop/Single
谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!
标签:
相关文章
-
无相关信息