目录
1.单例模式
1.1饿汉模式
举个例子解释饿汉模式:中午使用了三个菜碟,吃完后,马上把三个碗都洗了。
通过Singleton 这个类 来实现单例模式,保证 Singleton 这个类只有唯一实例
在下面程序中使用到的 static。 static 修饰的成员更加准确的说,是“类成员”=>“类属性/类方法”。一个java 程序中,一个类对象只存在一份(JVM保证的),进一步的也就保证了类的 static 成员也是只有一份的。
//通过Singleton 这个类 来实现单例模式,保证 Singleton 这个类只有唯一实例
/**
* 饿汉模式
* 通过Singleton 这个类 来实现单例模式,保证 Singleton 这个类只有唯一实例
*/
class Singleton {
//1.使用static 创建一个实例,并且立即实例化
//这个instance 对应的实例,就是该类的唯一实例
private static Singleton instance = new Singleton();
//2.为了防止程序员在其他地方不小心 new 这个 Singleton ,就可以把构造方法设为private
private Singleton() {};
//3.提供一个方法,让外面(main方法里)拿到唯一实例
public static Singleton getInstance() {
return instance;
}
}
public class Test18 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
}
}
上诉代码的针对唯一的实例初始化,比较着急,在类加载阶段,就会直接创建实例(程序中用到这个类,就会立即加载),实例就行下面程序所示

饿汉模式中 getInstance,仅仅是读取了变量的内容,如果多个线程只是读取一个变量,不修改,此时任然是线程安全的。如下所示
1.2懒汉模式
举个例子解释懒汉模式:中午使用了三个菜碟,吃完后,先放着,晚上需要一个就洗一个,需要两个就洗两个,剩下的不管。
相对于饿汉模式来说,懒汉模式更加高效。
如下代码是完全体的线程安全单例模式,具有1.正确的位置加锁,2.双重 if 淡定。3.volatile
/**
* 实现单例模式 - 懒汉模式
*/
class Singleton2 {
//1.把不是立即就初始实例
private static volatile Singleton2 instance2 = null;
//2.把构造方法设为 private
private Singleton2() {}
//3.提供一个方法来获取到上述单例模式
//只有当真正需要到这个 实例 的时候,才会真正去创建这个实例
public static Singleton2 getInstance() {
// 如果这个条件成立, 说明当前的单例未初始化过的, 存在线程安全风险, 就需要加锁~~
if (instance2 == null) {
synchronized (Singleton2.class) {
if (instance2 == null) {
instance2 = new Singleton2();
}
}
}
return instance2;
}
}
public class Test19 {
public static void main(String[] args) {
Singleton2 instance2 = Singleton2.getInstance();
}
}
懒汉模式中即包含了读,又包含了修改。但是这里的读和修改,是分为两个步骤的(不是原子性),存在线程安全问题,,因此需要加锁synchronized。加锁可能导致代码出现阻塞,外层条件可能执行几分钟几分钟之后里层才开始执行。在这个执行的时间差中间,instance也可能被其他线程给修改的。
2.阻塞队列
当队列满的时候 , 继续入队列就会阻塞 , 直到有其他线程从队列中取走元素 .当队列空的时候 , 继续出队列也会阻塞 , 直到有其他线程往队列中插入元素 .
阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型。
比如在 " 秒杀 " 场景下 , 服务器同一时刻可能会收到大量的支付请求 . 如果直接处理这些支付请求 ,服务器可能扛不住( 每个支付请求的处理都需要比较复杂的流程 )。 这个时候就可以把这些请求都放到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求。这样做可以有效进行 " 削峰 ", 防止服务器被突然到来的一波请求直接冲垮。
(2)阻塞队列也能使生产者和消费者之间解耦。
比如做饭吃。在一个家里都是有明确分工的,一个人负责买菜回来洗,另一个人负责将菜煮熟。
那么买菜回来洗的人就是 “生产者”,煮菜的人就是“消费者”。
两个人各干各的,有就做。
应用:
生产者消费者模型,是实际开发中非常有用的一种多线程开发手段,尤其是在服务器开发场景中。
假设,有两个服务器A、B。A作为入口服务器直接接收用户的网络请求,B作为应用服务器,来给A提供数据。
1)通常情况下
未使用生产者消费者模型如下图所示
如果不使用生产者消费者模型,此时的A和B的耦合性是比较强的,在开发A代码的时候就得到充分了解B的一些接口。开发B代码的时候也得充分了解到A是怎样调用的。一旦想把B换成C,A的代码就需要较大的改动。如果B没了,也可能直接导致A也没了。
因此,使用生产者消费者模型,就可以降低这里的耦合,如下图所示
优点一:能够让多个服务器程序之间更充分的解耦合
对于请求:A是生产者,B是消费者
对于响应:A是消费者,B是生产者
阻塞队列是作为交易场所。
A只需要关注如何与队列交互,不需要认识B。
B也只需要关注如何与队列交互,也不需要认识A。
A与B直接互不影响。
2)暴涨情况下
未使用生产者消费者模型如下图所示
当
当请求量突然暴涨不可控的时候,A作为入口服务器,计算量很轻,请求暴涨,问题不大。但是B作为应用服务器,计算量可能很大,需要的系统资源也更多,如果请求更多了,需要的资源进一步增加,如果主机的硬件不够,可能程序就撑不住了。
使用生产者消费者模型如下图所示
优点二:能够对于请求进行“削峰填谷”
当A请求暴涨时=>阻塞队列的请求暴涨。由于阻塞队列没什么计算量,就单纯的存个数据,就能抗住跟大的压力。B任然按照原来的速度来消费数据,不会因为A的暴涨而引起暴涨。
“削峰” 指的是峰值很多时候不是持续的,就一阵子,过去了就又恢复了。
“填谷” B任然按照原来的频率来处理之前的积压数据。
2.1阻塞队列的实现(生产者消费者模型)
class MyBlockingQueue {
//保存数据的本体
private int[] data = new int[1024];
//有效元素个数
private int size = 0;
//队列下标
private int head = 0;
//队尾下标
private int tail = 0;
//专门的锁对象
private Object locker = new Object();
// 入队列
public void put(int value) throws InterruptedException{
synchronized (locker) {
if (size == data.length) {
//队列满了,暂时先直接返回。
//return
locker.wait();
}
//把新的元素放到 tail 位置上
data[tail] = value;
tail++;
//处理tail 到达数组末尾的情况
if (tail >= data.length) {
tail = 0;
}
//tail = tail % data.length;
size++;//插入完成之后要修改元素个数
//如果入队成功,则队列排空,于是就唤醒take 中的阻塞等待
locker.notify();
}
}
//出队列
public Integer take() throws InterruptedException {
synchronized (locker) {
if (size == 0) {
//如果队列为空,就返回一个非法值
//return null;
locker.wait();
}
//取出head 位置的元素
int ret = data[head];
head++;
if (head >= data.length) {
head = 0;
}
size--;
//take 成功之后。就唤醒put 中的等待
locker.notify();
return ret;
}
}
}
/**
* 实现一个简单的生产者消费者模型
*/
public class Test21 {
private static MyBlockingQueue queue = new MyBlockingQueue();
public static void main(String[] args) throws InterruptedException {
Thread producer = new Thread(() -> {
int num = 0;
while (true) {
System.out.println("生产了 :" + num);
try {
queue.put(num);//入队
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
}
}) ;
producer.start();
Thread customer = new Thread(() -> {
while (true) {
try {
int num = queue.take();
System.out.println("消费了 : " + num);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customer.start();
}
}