关于前端:JavaEE初阶多线程-基础篇-单例模式案例一

28次阅读

共计 4304 个字符,预计需要花费 11 分钟才能阅读完成。

写在后面

后面曾经向各位铁铁们介绍了 对于多线程的一些根本的知识点,为了能够让大家更好的了解 多线程的一些相干的个性,这篇博客 就会联合着一些具体的代码案例 来向大家介绍~

这篇博客就向大家介绍 对于多线程的第一个案例 —— 单例模式~

一、单例模式的概念

所谓单例模式,是一种常见的设计模式~

单例模式心愿:有些对象,在一个程序中应该只有惟一一个实例,就能够应用单例模式~

换句话说,在单例模式下,对象的实例化被限度了,只能创立一个,多了也创立不了~

如果关是靠人来保障,是不靠谱的,所以就借助语法,强行限度不能够创立多个实例,防止程序员不小心出错~

设计模式:相似于棋谱,是 前辈们曾经总结好了的一些固定套路,照着棋谱来下棋,棋就不会下的太差,这就进步了上限(比如说,象棋 ……)~

二、单例模式的简略实现

在 Java 外面的单例模式,有很多种实现形式,在这里次要介绍两个大类:饿汉模式、懒汉模式~

饿汉模式 和 懒汉模式 两种模式,形容了创立实例的工夫~

这两种模式,并没有什么高低贵贱之分,不是 现实生活中的 “ 贬义词 ”,相同 在计算机领域,” 懒 ” 还是一个褒义词,这个字意味着计算机的性能比拟高~

在计算机中,这种思维是很常见的~

比如说,想要理解某个材料(大文件 10G,寄存在硬盘中),那么 此时应用某个编辑器,关上文件,就会呈现两种状况:

  1. [饿汉] 把 10G 都读到内存中,读取结束之后 再容许用户进行查看和批改~
  2. [懒汉] 只读取一点点(以后屏幕能显示出的范畴),随着用户翻页,持续再读后续内容~

所以说,如果是 饿汉模式,那么显示所须要的工夫就会比拟多;如果是 懒汉模式,那么显示的工夫就会比拟低,效率就会比拟高(也有可能 用户关上文件当前,只看了两眼就关了,前面的大部分都没有读,那么内存读了那么多也没有意义)~

所以,通常咱们都认为,懒汉模式 要比 饿汉模式 更高效~

当然,还有 刷抖音、看小说、看微信、上网浏览内容 …… 的时候,都是借鉴了 懒汉模式~

2.1 饿汉模式

饿汉模式 的意思是,程序一旦启动,就会立即创立实例~

这就好比,一个饿了的人,看到一张饼,就会急不可待的往嘴里塞,咱们把它叫做 “ 饿汉 ”~

static 关键字 的前因后果:

static 名字叫做 “ 动态 ”,然而实际上和字面意思没有任何的关系,这是一个历史遗留的问题~

理论示意的含意是 “ 类属性 / 类办法 ”,同样的,咱们把 不是动态的一般的成员叫做 “ 实例属性 / 实例办法 ”~

Java 外面叫做 “ 动态 ” 是因为 C++ 外面示意 “ 类属性 ”,就是用 static,Java 是从 C++ 那里抄来的;而 C++ 则是因为 引入面向对象之后。须要搞一个形式来定义类属性,就须要引入一个关键字,然而引入新的关键字 老本极高,所以关键字设计者的大佬们 眼光就盯住了 旧的关键字~

于是,static 就中招了,static 原来示意的是 变量放到动态内存区,然而随着工夫的推移,零碎的进化,曾经没有 “ 动态内存区 ” 这个说法了,然而 static 关键字 还在,于是 “ 旧瓶装新酒 ”,就用来示意 “ 类属性 / 类办法 ” 了~

此时,” 类属性 / 类办法 ” 和 静不动态 字面意思上没有啥关系,只是 轻易找一个之前旧的关键字,当初没啥用了,赋予一个新的性能,仅此而已~

引入新的关键字老本极高的起因:

写代码的时候,变量名不能和关键字一样,当引入新的关键字的时候,不能够确定 其他人是不是曾经引入了 新的关键字 作为变量名(全世界的 C++ 代码那么多 ……)~

更大的可能是 新的关键字一引入,就会导致已有的一些代码 编译就会失败~

而后这把火就会烧到了 关键字设计者 的身上~

而类属性就长在类对象上,类对象在整个程序中只有惟一一个实例(JVM 保障的),所以说 类的动态成员就只有惟一一个了~

package thread;

// 单例模式,饿汉的形式
class Singleton {private static Singleton instance = new Singleton();

    // 后续如果须要这个实例,就须要对立基于 getInstance 办法来获取 实力独苗,不要去 new 了~
    public static Singleton getInstance() {return instance;}

    // 构造方法设为 公有,此时 其余的类想来 new 就不能够了 (通过 编译器的规定来确保只有一个实例对象)~
    private Singleton() {}
}
public class Demo19 {public static void main(String[] args) {
        // 饿汉模式 的调用
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
    }
}

运行后果:

如果不小心 想创立另一个实例,那么就会编译报错了:

总结:

应用 动态成员示意实例(唯一性)+ 让构造方法设为公有(堵住了 new 创立新实例的口子)~

依照下面的代码,当 Singleton 类 被加载的时候,就会执行到 实例化操作,此时 实例化的机会十分早(十分迫切的感觉),咱们把它称为 饿汉模式~

对于饿汉模式来说,在多线程的状况下,屡次调用的是  getInstance() 办法,而 这个办法只是一个 读操作,对于多线程读操作来说,是线程平安的~

2.2 懒汉模式

懒汉模式 的意思是,程序启动,先不焦急创立实例,等到真正用的时候,再创立实例~

这个也很形象,比拟 “ 懒 ”,不想干活,等到须要的时候再去干活~

// 懒汉模式的实现
class SingletLazy {
    // 此处没有立刻创立实例
    private static SingletLazy instance = null;

    // 当首次调用 getInstance() 的时候,才会创立实例
    public static SingletLazy getInstance() {if (instance == null) {instance = new SingletLazy();
        }
        return instance;
    }

    // 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
    private SingletLazy() {}
}

在多线程的状况下,懒汉模式,屡次调用 getInstance() 办法,而且波及到了 两次读操作(读出 instance 是否为空,读出 返回的 instance 值)和 一次写操作(批改 instance 变量的值),这是线程不平安的~

当然,一旦实例创立好了当前,后续 if 条件语句就进不去了,此时也就是 全是读操作了,也就线程平安了~

既然曾经明确了,懒汉模式 是线程不平安的,那么 如何解决懒汉模式线程不平安的问题呢?

方法就是 须要加锁!!!

通过 加锁 来保障 “ 判断 ” 和 “ 批改 ” 这组操作是原子的~

// 懒汉模式的实现
class SingletLazy {
    // 此处没有立刻创立实例
    private static SingletLazy instance = null;

    // 当首次调用 getInstance() 的时候,才会创立实例
    public static SingletLazy getInstance() {synchronized (SingletLazy.class) {if (instance == null) {instance = new SingletLazy();
            }
        }
        return instance;
    }

    // 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
    private SingletLazy() {}
}

懒汉模式,只是在初始状况下,才会有线程不平安的问题,一旦实例创立好了当前,此时就平安了~

所以说,在后续调用 getInstance 的时候就不应该尝试加锁了~

如果应用上述的代码,无论 instance 是否为空(是否初始化),都会进行加锁,使得锁竞争加剧,耗费一些没有必要耗费的资源,就会很影响效率了~

在加锁之前,还须要进行判断 instance 是否为空(是否初始化),如果为空才会进行加锁:

// 懒汉模式的实现
class SingletLazy {
    // 此处没有立刻创立实例
    private static SingletLazy instance = null;

    // 当首次调用 getInstance() 的时候,才会创立实例
    public static SingletLazy getInstance() {if (instance == null) {synchronized (SingletLazy.class) {if (instance == null) {instance = new SingletLazy();
                }
            }
        }
        return instance;
    }

    // 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
    private SingletLazy() {}
}

剖析:

外层 if 断定以后是否曾经初始化好,如果未初始化好,就尝试加锁;如果曾经初始化好,那么就接着往下走~

里层 if 是在多个线程尝试初始化,产生了锁竞争,这些参加竞争的线程 拿到锁之后,再进一步确认,是否真的要初始化~

当然,下面的代码操作还是有一些问题的 —— 有的线程在读,有的线程在写~

这就联想起了 —— 内存可见性问题~

其实,这里的状况 和 之前的状况还不一样,每一个线程都有本人的上下文,都有本人的寄存器内容,按理来说 是不应该会呈现优化的~

然而,实际上也不好说,也并不能保障 编译器优化 是啥样的过程~

因而,给 instance 加上 volatile 是更加持重的做法~

如果不加 volatile 不肯定会有问题,然而 稳当起见,还是加上更好~

// 懒汉模式的实现
class SingletLazy {
    // 此处没有立刻创立实例
    volatile private static SingletLazy instance = null;

    // 当首次调用 getInstance() 的时候,才会创立实例
    public static SingletLazy getInstance() {if (instance == null) {synchronized (SingletLazy.class) {if (instance == null) {instance = new SingletLazy();
                }
            }
        }
        return instance;
    }

    // 同理,创立构造方法 SingletLazy(),避免该类实例化其余的对象
    private SingletLazy() {}
}

总结:

懒汉模式 线程的三个要点:

  1. 加锁
  2. 双重 if
  3. volatile(不加可能是错的,但加了肯定是正确的)

对于 单例模式 的内容就先介绍到这里了,当然,在 Java 中也有许多其余的形式 也能够实现单例模式,如 基于枚举、基于外部类 等等~

如果感觉这一篇博客对你有帮忙的话,能够一键三连走一波,十分非常感谢啦 ~

正文完
 0