多线程案例(代码要熟练掌握)
文章目录
单例模式
单例模式能保证某个类在程序中只存在唯一 一份实例, 而不会创建出多个实例.
这一点在很多场景上都需要. 比如 JDBC
中的 DataSource
实例就只需要一个.
单例模式具体的实现方式,分成饿汉和懒汉两种.
饿汉模式:static
在类加载阶段就把实例创建出来.
懒汉模式:通过getlnstance
方法来获取到实例.首次调用该方法的时候,才真正创建实例. (懒加载延时加载)
/**
* 饿汉模式
*/
public class ThreadDemo20 {
static class Singleton{
//把构造方法设为private,防止类外面调用构造方法,也就防止了调用者再其他地方创建该实例
private Singleton(){
}
private static Singleton instance = new Singleton();
private static Singleton getInstance(){
return instance;
}
}
}
如果多个线程调用getInstance
,是否出现线程安全问题??
对于饿汉模式的代码,是线程安全的.当前是多个线程读操作,不涉及修改操作
/**
* 懒汉模式
*/
public class ThreadDemo21 {
static class Singleton{
private Singleton(){
}
private static Singleton instance = null;
public static Singleton getInstance(){
//instance == null相当于读操作
if (instance == null){
//赋值相当于修改操作
instance = new Singleton();
}
return instance;
}
}
public static void main(String[] args) {
//通过这个方法来获取的实例,就能保证只有唯一的实例
Singleton instance = Singleton.getInstance();
}
}
如果实例已经创建完毕,后续再调用getInstance
,此时不涉及修改操作.此时仍然是线程安全的
但是如果实例尚未创建,此时就可能会涉及修改.如果确实存在多个线程同时修改,就会涉及到线程安全问题!!!
阻塞式队列
阻塞队列是什么
阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.
阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:
当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.
阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
/**
* 阻塞队列
*/
public class ThreadDemo22 {
public static void main(String[] args) throws InterruptedException {
//BlockingQueue是一个接口是不可以直接实例化的,但LinkedBlockingDeque是基于它实现的
//10代表最大容量,超过了继续插入就会阻塞
BlockingQueue<String> queue = new LinkedBlockingDeque<>(10);
//虽然offer和put都可以有插入功能,但是offer不提供阻塞功能.所以这里更推荐使用put
//queue.offer("hello");
queue.put("hello");
//取元素
String elem = queue.take();
System.out.println(elem);
elem = queue.take();//只有一个元素,所以代码运行到这里就阻塞了
System.out.println(elem);
}
}
生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.
生产者消费者模型最大的用途:1.解耦合,2.削峰填谷
- 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.
比如在 “秒杀” 场景下, 服务器同一时刻可能会收到大量的支付请求. 如果直接处理这些支付请求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程). 这个时候就可以把这些请求都放到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求. 这样做可以有效进行 “削峰”, 防止服务器被突然到来的一波请求直接冲垮.
现实生活中就像三峡大坝,汛期如果没有大坝,下游的水就很大,可能就有水灾(关闸蓄水功能相当于削峰),旱期如果没有大坝,下游的水就很少,可能就旱灾(开闸放水相当于填谷)
- 阻塞队列也能使生产者和消费者之间 解耦合.
比如过年一家人一起包饺子. 一般都是有明确分工, 比如一个人负责擀饺子皮, 其他人负责包. 擀饺子皮的人就是 “生产者”, 包饺子的人就是 “消费者”.
擀饺子皮的人不关心包饺子的人是谁(能包就行, 无论是手工包, 借助工具, 还是机器包), 包饺子的人也不关心擀饺子皮的人是谁(有饺子皮就行, 无论是用擀面杖擀的, 还是拿罐头瓶擀, 还是直接从超市买的).
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 生产者,消费者模型
*/
public class ThreadDemo23 {
public static void main(String[] args) {
//使用BlockingQueue作为交易场所
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
//搞一个线程作为消费者
Thread customer = new Thread(){
@Override
public void run() {
while (true) {
//取队首元素
try {
Integer value = queue.take();
System.out.println("消费元素:" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
customer