
🔥个人主页:寻星探路 🎬作者简介:Java研发方向学习者 📖个人专栏: 、《 ⭐️人生格言:没有人生来就会编程,但我生来倔强!!!
单例模式是校招中最常考的设计模式之一
啥是设计模式? 设计模式好比象棋中的"棋谱",红方当头炮,⿊方马来跳,针对红方的一些走法,黑方应招的时候有一些固定的套路,按照套路来⾛局势就不会吃亏。 软件开发中也有很多常见的"问题场景",针对这些问题场景,大佬们总结出了⼀些固定的套路,按照这个套路来实现代码,也不会吃亏
单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例
这一点在很多场景上都需要,比如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;
}
}上面的懒汉模式的实现是线程不安全的
线程安全问题发⽣在首次创建实例时,如果在多个线程中同时调用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;
}
}以下代码在加锁的基础上,做出了进一步改动:
使用双重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挡住了,也就不会继续创建其他实例
1. 有三个线程,开始执行getInstance ,,通过外层的 if (instance == null) 知道了实例还没有创建的消息,于是开始竞争同⼀把锁

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

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

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

阻塞队列是一种特殊的队列,也遵守"先进先出"的原则。
阻塞队列能是一种线程安全的数据结构,并且具有以下特性:
当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素
当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素
阻塞队列的一个典型应用场景就是"生产者消费者模型",这是⼀种非常典型的开发模型。
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。
1)阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力(削峰填⾕)
比如在"秒杀"场景下,服务器同一时刻可能会收到大量的支付请求,如果直接处理这些⽀付请求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程),这个时候就可以把这些请求都放到一个阻塞队列中,然后再由消费者线程慢慢的来处理每个支付请求。 这样做可以有效进行"削峰",防止服务器被突然到来的一波请求直接冲垮。
2)阻塞队列也能使生产者和消费者之间解耦
比如过年一家人一起包饺子,一般都是有明确分工,比如⼀个人负责擀饺子皮,其他人负责包,擀饺子皮的人就是"生产者",包饺子的人就是"消费者"。 擀饺子皮的人不关心包饺子的人是谁(能包就行,无论是手工包,借助工具,还是机器包),包饺子的人也不关心擀饺子皮的人是谁(有饺子皮就行,无论是用擀面杖擀的,还是拿罐头瓶擀,还是直接从超市买的)
在Java标准库中内置了阻塞队列,如果我们需要在一些程序中使用阻塞队列,直接使用标准库中的即可
BlockingQueue是一个接口,真正实现的类是LinkedBlockingQueue
put方法用于阻塞式的入队列,take用于阻塞式的出队列
BlockingQueue也有offer、poll、peek等方法,但是这些方法不带有阻塞特性
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// ⼊队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();生产者消费者模型
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();
}通过"循环队列"的方式来实现
使用synchronized进行加锁控制
put插入元素的时候,判定如果队列满了,就进行wait。(注意,要在循环中进行wait.被唤醒时不⼀定队列就不满了,因为同时可能是唤醒了多个线程)
take取出元素的时候,判定如果队列为空,就进行wait。(也是循环wait)
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();
}
}由于内容较多,会分为多篇讲解,预知后续内容,请看后续博客!!!