单例模式是校招中最常考的设计模式之一
啥是设计模式?
设计模式好比象棋中的"棋谱",红方当头炮,⿊方马来跳,针对红方的一些走法,黑方应招的时候有一些固定的套路,按照套路来⾛局势就不会吃亏。
软件开发中也有很多常见的"问题场景",针对这些问题场景,大佬们总结出了⼀些固定的套路,按照这个套路来实现代码,也不会吃亏 单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例
这一点在很多场景上都需要,比如JDBC中的DataSource实例就只需要一个
单例模式具体的实现方式有很多,最常见的是"饿汉"和"懒汉"两种
类加载的同时创建实例
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}类加载的时候不创建实例,第一次使用的时候才创建实例
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}懒汉的问题:
左边是t1 右边是t2
这说好的创建一次实例呢,这不就是创建两次了么(这虽然被覆盖了,而且随着第二次的操作覆盖了,第一个线程new 的对象,也会被GC给释放掉)

上面的懒汉模式的实现是线程不安全的
线程安全问题发生在首次创建实例时,如果在多个线程中同时调用 getInstance方法,就可能导致创建出多个实例。 一旦实例已经创建好了,后面再多线程环境调用getInstance就不再有线程安全问题了(不再修改 instance 了)
加上synchronized可以改善这⾥的线程安全问题
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}当然把他synchronized 修饰方法也可以实现

这段代码是懒汉式单例的 getInstance 方法,通过 synchronized (Locker) 加锁,确保多线程下只有一个线程能创建实例(当 instance == null 时,执行 instance = new SingletonLazy()),从而保证单例的唯一性。
当实例创建完成后,后续所有调用 getInstance 的线程:
if (instance == null) 判断 + return instance”(纯粹的读操作,无线程安全风险)。这种实现 “过度加锁”—— 在不需要线程安全保护的读操作阶段,仍强制进行锁竞争,从而产生不必要的性能开销。
(可进一步优化为 “双重检查锁” 模式,即先判断 instance == null 再加锁,避免后续读操作的无效加锁,平衡线程安全与执行效率。)
但是效率就慢了
但是还有一种隐藏的问题就是:
他这里有可能是 进行编译器优化,这个是广义的编译器,javac ,jvm 甚至是操作系统都要配合的
进行编译器优化,这里的指令运行顺序就会被打乱,但是逻辑是一样的,但是这个指令被打乱,出现误判,就会出现多线程的问题

以下代码在加锁的基础上,做出了进一步改动:
使用双重if判定,降低锁竞争的频率
给instance加上了volatile
class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}理解双重if判定/volatile: 加锁/解锁是⼀件开销比较⾼的事情,而懒汉模式的线程不安全只是发生在首次创建实例的时候,因此后续使⽤的时候,不必再进行加锁了。 外层的if就是判定下看当前是否已经把instance实例创建出来了。 同时为了避免"内存可见性"导致读取的instance出现偏差,于是补充上volatile。 当多线程首次调用getInstance,大家可能都发现instance为null,于是又继续往下执行来竞争锁,其中竞争成功的线程,再完成创建实例的操作。 当这个实例创建完了之后,其他竞争到锁的线程就被里层if挡住了,也就不会继续创建其他实例
创建对象(instance = new SingletonLazy())在 JVM 中会被拆分为 3 步核心指令:
instance 指向刚分配的内存,此时 instance != null)。JVM 为了优化性能,可能会对这 3 步指令重排序(不影响单线程执行结果),比如重排为:1 → 3 → 2(先分配内存、让引用指向地址,再初始化对象)。
private static volatile SingletonLazy instance; // 关键:volatile修饰假设线程 A 执行到 “创建实例” 时,指令被重排为 1→3→2:
instance 已非空),但步骤 2(对象初始化)尚未完成;getInstance 方法,第一次检查 instance != null,直接返回这个未初始化完成的对象;volatile 禁止重排序在单例变量 instance 前添加 volatile 关键字,可解决此问题:
1. 有三个线程,开始执行getInstance ,,通过外层的 if (instance == null) 知道了实例还没有创建的消息,于是开始竞争同⼀把锁


2. 其中线程1率先获取到锁,此时线程1通过里层的 if (instance == null) 进一步确认实例是否已经创建,如果没创建,就把这个实例创建出来。

3. 当线程1释放锁之后,线程2和线程3也拿到锁,也通过里层的 if (instance == null) 来确认实例是否已经创建,发现实例已经创建出来了,就不再创建了。

4. 后续的线程,不必加锁,直接就通过外层 if (instance == null) 就知道实例已经创建了, 从⽽不再尝试获取锁了.降低了开销

阻塞队列能是一种线程安全的数据结构,并且具有以下特性:
当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素
当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素
阻塞队列的一个典型应用场景就是"生产者消费者模型",这是⼀种非常典型的开发模型。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。
1)阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力(削峰填⾕)
比如在"秒杀"场景下,服务器同一时刻可能会收到大量的支付请求,如果直接处理这些⽀付请求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程),这个时候就可以把这些请求都放到一个阻塞队列中,然后再由消费者线程慢慢的来处理每个支付请求。
这样做可以有效进行"削峰",防止服务器被突然到来的一波请求直接冲垮。
2)阻塞队列也能使生产者和消费者之间解耦
比如过年一家人一起包饺子,一般都是有明确分工,比如⼀个人负责擀饺子皮,其他人负责包,擀饺子皮的人就是"生产者",包饺子的人就是"消费者"。
擀饺子皮的人不关心包饺子的人是谁(能包就行,无论是手工包,借助工具,还是机器包),包饺子的人也不关心擀饺子皮的人是谁(有饺子皮就行,无论是用擀面杖擀的,还是拿罐头瓶擀,还是直接从超市买的)
BlockingQueue是一个接口,真正实现的类是LinkedBlockingQueue
put方法用于阻塞式的入队列,take用于阻塞式的出队列
BlockingQueue也有offer、poll、peek等方法,但是这些方法不带有阻塞特性
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// ⼊队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();
执行到 队列已经满了,在queue.put()在这就已经堵塞了,后面就不会执行了
生产者消费者模型
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println("消费元素: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
int num = random.nextInt(1000);
System.out.println("⽣产元素: " + num);
blockingQueue.put(num);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "⽣产者");
producer.start();
customer.join();
producer.join();
}

4、阻塞队列的实现
通过"循环队列"的方式来实现
使用synchronized进行加锁控制
put插入元素的时候,判定如果队列满了,就进行wait。(注意,要在循环中进行wait.被唤醒时不⼀定队列就不满了,因为同时可能是唤醒了多个线程)
判定这里面的逻辑是否还是正确的
二次验证,这里的wait 是否还是 size = 0
wait 有可能被NotifyAll()的 唤醒 或者 被Interrupter
public class BlockingQueue {
private int[] items = new int[1000];
private volatile int size = 0;
private volatile int head = 0;
private volatile int tail = 0;
public void put(int value) throws InterruptedException {
synchronized (this) {
// 此处最好使⽤ while.
// 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
// 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能⼜已经队列满了
// 就只能继续等待
while (size == items.length) {
wait();
}
items[tail] = value;
tail = (tail + 1) % items.length;
size++;
notifyAll();
}
}
public int take() throws InterruptedException {
int ret = 0;
synchronized (this) {
while (size == 0) {
wait();
}
ret = items[head];
head = (head + 1) % items.length;
size--;
notifyAll();
}
return ret;
}
public synchronized int size() {
return size;
}
// 测试代码
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new BlockingQueue();
Thread customer = new Thread(() -> {
while (true) {
try {
int value = blockingQueue.take();
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
customer.start();
Thread producer = new Thread(() -> {
Random random = new Random();
while (true) {
try {
blockingQueue.put(random.nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "⽣产者");
producer.start();
customer.join();
producer.join();
}
}消息队列的一点点扩展

package thread;
import java.util.Arrays;
public class demo31 {
public static void main(String[] args) {
MyBlockingQueue queue = new MyBlockingQueue(1000);
Thread producer = new Thread(() -> {
int n = 0;
while (true) {
try {
queue.put(n + "");
System.out.println("生产元素 " + n);
n++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread consumer = new Thread(() -> {
while (true) {
String n = null;
try {
n = queue.take();
System.out.println("消费元素 " + n);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
producer.start();
consumer.start();
}
}
class MyBlockingQueue {
//用数组实现这个queue
public String[] array = null;
public volatile int head = 0;
public volatile int tail = 0;
public volatile int size = 0;
public MyBlockingQueue(int Capitial){
array = new String[Capitial];
}
public void put(String string) throws InterruptedException {
synchronized (this) {
while (size == array.length) {
wait();
}
array[tail] = string;
tail++;
if (tail >= array.length) {
tail = 0;
}
notify();
}
}
public String take() throws InterruptedException {
synchronized (this) {
while (size == array.length) {
wait();
}
String ret = array[head];
array[head] = null;
head++;
if(head >= array.length){
head = 0;
}
size--;
notify();
return ret;
}
}
}