Skip to content

Commit

Permalink
完成《单例模式》
Browse files Browse the repository at this point in the history
  • Loading branch information
971230 committed Sep 20, 2024
1 parent 43c4d58 commit 8be55e9
Showing 1 changed file with 118 additions and 2 deletions.
120 changes: 118 additions & 2 deletions docs/开始阅读/设计模式/单例模式.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public class SingletonOfLazy {
private static SingletonOfLazy INSTANCE = null;

private SingletonOfLazy() {
if (INSTANCE != null) {
throw new RuntimeException("单例对象无法重复创建");
}
}

public static SingletonOfLazy getInstance() {
Expand All @@ -48,8 +51,121 @@ public class SingletonOfLazy {
}
```

# ➋ 饿汉式
## ➋ 饿汉式

<P style="text-indent:2em;">
饿汉式就是无论你用不用的到,我都先创建对象,你要用就直接用,比喻使用者饿了,不用等待创建过程直接“吃”。
</p>

<P style="text-indent:2em;">
饿汉式就是无论你用不用的到,我都先创建对象,你要用就直接用,比喻使用者饿了,不要等待创建过程直接“吃”。
首先它在类加载时就创建实例,同时Java的类加载机制确保了类是线程安全的;<br>
然后私有构造函数,防止外部实例化;<br>
最后提供全局访问点以获取对象。
</p>

```java
public class SingletonOfHungry {
private static final SingletonOfHungry INSTANCE = new SingletonOfHungry();

private SingletonOfHungry() {
if (INSTANCE != null) {
throw new RuntimeException("单例对象无法重复创建");
}
}

public static SingletonOfHungry getInstance() {
return INSTANCE;
}
}
```

## ➌ 双检锁懒汉式

<P style="text-indent:2em;">
前面两种,一种懒汉式,用到才创建,避免了对象创建而长时间不使用的问题,但是它有线程安全问题;
一种饿汉式,直接创建对象,将创建对象的资源消耗放到类加载时,也避免了线程安全问题,但是对象一直在内存里而不使用,
会有浪费。两者各有优劣。
</p>

<P style="text-indent:2em;">
双检锁懒汉式是正对懒汉式线程安全问题的一种改良,很好的体现了对于锁🔒的理解。使用起来很简单,理解里面的意思就难了,
比如两次判断的含义,也算是面试会考的题目了。
</p>

```java
public class SingletonOfDCLLazy {
/**
* 用volatile主要是为了保证变量的有序性,因为CPU会自己优化代码执行顺序,
* 单线程下无所谓,多线程下会有问题,而volatile可以防止指令重排,它会在赋值操作后加上一个内存屏障,
* 这样就可以让之前的操作不允许越过这个赋值操作来执行,而防止CPU的自动优化.
*/
private static volatile SingletonOfDCLLazy INSTANCE = null;

private SingletonOfDCLLazy() {
//构造器必须私有 不然直接new就可以创建
}

public static SingletonOfDCLLazy getInstance() {
// 1️⃣第一次判断,假设会有好多线程,如果doubleLock没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
// 如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
if (INSTANCE == null) {
synchronized (SingletonOfDCLLazy.class) {
// 2️⃣第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入,然后判断doubleLock是null,
// 他就实例化了doubleLock,然后他出了锁,
// 这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new DoubleLock(),
// 再创建一个实例,所以为了防止这种情况,需要第二次判断
if (INSTANCE == null) {
// 下面这句代码其实分为三步:
// 1.开辟内存分配给这个对象
// 2.初始化对象
// 3.将内存地址赋给虚拟机栈内存中的doubleLock变量
// 注意上面这三步,第2步和第3步的顺序是随机的,这是计算机指令重排序的问题
// 假设有两个线程,其中一个线程执行下面这行代码,如果第三步先执行了,就会把没有初始化的内存赋值给doubleLock
// 然后恰好这时候有另一个线程执行了第一个判断if(doubleLock == null),然后就会发现doubleLock指向了一个内存地址
// 这另一个线程就直接返回了这个没有初始化的内存,所以要防止第2步和第3步重排序
INSTANCE = new SingletonOfDCLLazy();
}
}
}
return INSTANCE;
}
}
```
## ➍ 内部类单例

<P style="text-indent:2em;">
这个模式使自己比较喜欢的,没有那么多弯弯绕,利用Java的静态内部类机制来实现懒加载和线程安全,用起来也方便。
</p>

```java
public class SingletonOfInnerClass {
private SingletonOfInnerClass() {
}

private static class Holder {
static SingletonOfInnerClass INSTANCE = new SingletonOfInnerClass();
}

public static SingletonOfInnerClass getInstance() {
return Holder.INSTANCE;
}
}
```

## ➎ 枚举单例

<P style="text-indent:2em;">
Java的枚举类型天然支持单例模式,并且是线程安全的,就是使用范围小,只能说算是,但是你要写一个单例,
枚举肯定不在考虑范围,枚举的设计就不是为单例而生,只是“凑巧”它天然是单例。
</p>

```java
public enum SingletonOfHungryEnum {
INSTANCE;

// 其他方法
public void someMethod() {
// 方法实现
}
}
```

0 comments on commit 8be55e9

Please sign in to comment.