过气的,终究是过气了
上一章简单介绍了 UML 类图(二), 如果没有看过,请观看上一章
一. 单例模式
所谓的单例设计模式,就是采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,
并且该类只提供一个取得其对象实例的方法 (静态方法)
一.一 单例模式介绍
引用 菜鸟教程里面的单例模式介绍: https://www.runoob.com/design-pattern/singleton-pattern.html
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,
它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,
可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
一.二 介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应用实例:
- 1、一个班级只有一个班主任。
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项: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;
}
}
测试方法:
@Test
public 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;
}
}
@Test
public 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;
}
}
测试方法:
@Test
public void threeTest() {
LanSingle03 single01 = LanSingle03.getInstance();
LanSingle03 single02 = LanSingle03.getInstance();
log.info("是否相同: {}", single01 == single02);
log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
}
但是该方式在多线程环境下会存在并发问题
@Test
public 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;
}
}
@Test
public 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;
}
}
@Test
public void fiveTest() {
LanSingle05 single01 = LanSingle05.getInstance();
LanSingle05 single02 = LanSingle05.getInstance();
log.info("是否相同: {}", single01 == single02);
log.info(" hashcode 是否相同: {}", single01.hashCode() == single02.hashCode());
}
有线程安全的问题:
@Test
public 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;
}
}
@Test
public 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;
}
}
测试方法:
@Test
public 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;
}
}
@Test
public 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 类
是饿汉式第一种
标签:getInstance,模式,single01,instance,实例,单例,public,single02 From: https://blog.51cto.com/yueshushu/6506184