目录
一:阻塞队列
模拟阻塞队列
二:线程池:
三:计数器:
定时器模拟实现
一:阻塞队列
阻塞队列是在原有的普通队列上做了扩充,标准库中原有的队列和子类都是线程不安全的。
1.线程安全
2.具有阻塞性
如果队列为空,进行出队列操作就会发生阻塞,直到有线程进入,对列不为空为止。
如果队列为满,进行入队列操作就会发生阻塞,直到对列不为空为止。
基于阻塞队列,最大的应用场景就是“生产者消费者模型”
生产者消费者模型有两大好处:
1.服务器之间的解耦合
2.起到削峰填谷的作用,在遇到请求量激增的情况下,可以保护下游服务器不会被请求冲垮。
代价:
1.需要更多机器来部署这样的消息队列
2.服务器之间的通信延迟会变长
模拟阻塞队列
//基于String进行存储,不考虑泛型
class MyBlockingQueue{
private String[] data = null;
private volatile int head = 0;
private volatile int tail = 0;
private volatile int size = 0;
private static Object lovker = new Object();
public MyBlockingQueue(int capacity){
data = new String[capacity];
}
public void put (String s) throws InterruptedException {
synchronized (lovker) {
if (size == data.length) {
while (size == data.length) {
lovker.wait();
}
data[tail] = s;
tail++;
if (tail >= data.length) {
tail = 0;
}
size++;
lovker.notify();
}
}
}
public String take() throws InterruptedException {
String ret = " ";
synchronized (lovker){
if(size == 0){
while (size == 0){
lovker.wait();
}
ret = data[head];
head++;
if(head >= data.length){
head = 0;
}
size--;
lovker.notify();
}
return ret;
}
}
二:线程池:
线程池的本质思想就是提高效率的。如果没有线程池,每次创建一个线程或者销毁一个线程都要调动操作系统内部的API,随着业务上对性能要求越来越高,线程的创建/销毁的频次越来越多,线程的开销就越来越大,无法再忽略不计了。
线程池就是解决上述问题的方案,线程池就是把线程提前从操作系统内部申请好,放到一个地方。后面需要使用线程,就直接从这个地方来取,而不是重新从操作系统中申请。同样的线程用完了也是回到这个地方。
为什么事先创建好线程放在那里已被待用更高效呢?
因为我们自己在编译器上写的代码属于用户态代码,由操作系统调用API创建线程是内核态的。
举个例子:
在银行办理事务时,如果在柜台的需要打印一张临时复印件,这个时候可以选择自己去门口复印,也可以让银行柜台内工作人员给你打印。
如果我自己去打印目的明确效率很高,整个过程连贯可控高效。
但是要柜台人员给我复印就会慢一些,因为在给我复印的时候有可能会有领导来给他布置任务,这时他可能会把我的事情滞后,先去完成领导布置的任务。同样的道理,在代码中也是一样,内核中的工作都是非常繁忙的,提交给内核的代码都是不可控的,效率往往比较低下。
标准库的线程池:java.util.concurrent
图中第四种最复杂,所以下面详细介绍第四种
在java里面线程分为两种,一种是核心线程(最少有多少个线程),一种是非核心线程(在线程扩容过程中新增的)。两者之和为最大线程数
1.int corePoolSize:核心线程数
int maximuumPoolSize:最大线程数
此线程可以支持“线程扩容”,某个线程初始情况下有M个线程,后来不够用,会再扩容N个线程。扩容的是非核心线程。
2.long keepAliveTime:线程存活的最大时间。
TimeUnit unit :存活的时间的单位。
3.BlockingQueue<Runnable> workQueue:工作队列。
Runnable代表要执行的任务,通过submit把要执行的任务设置到线程池内部的工作队列中。
Runnable接口本身要执行的含义就是一段可以执行的任务。
4.ThreadFactory threadFactor:线程工厂
工厂是指工厂设计模式,是在创建类的实例时使用的设计模式,是Thread类的工厂类,通过这个类完成Thread的实例创建和初始化操作。此处的ThreadFactor可以针对线程池里的线程进行批量操作。
为什么会有这个工厂?
因为多个版本的构造方法类名必须要保持一致,必须要重载
如果在直角坐标系下定义一个类
class Point{
public Point(double x,double y){.....} // 用于平面直角坐标系
public Point(double r,double a){......} //用于极坐标系
这俩方法无法构成重载,所以工厂模式就填补了上述的局限性。
5.RejectedExecutionHandler hander:拒绝策略 这是上述所有参数中最复杂的最重要的
如果队列元素满了,不要阻塞而是要明确拒绝线程
java标准库给出了四种不同的拒绝策略
ThreadPoolExecutor (定制性更强,用起来麻烦)线程池功能非常强大,标准库对其封装拉一下,Executors(定制性弱,用起来方便)提供了一些工厂方法,可以更方便的构造出线程池。线程池创建的都是前台线程。
优点:
- 降低资源消耗:减少线程的创建和销毁带来的性能开销。
- 提高响应速度:当任务来时可以直接使用,不用等待线程创建
- 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。
三:计数器:
相当于闹钟,在代码中往往需要程序定时执行,在java标准库中提供了定时器。
定时器模拟实现
除啦基于优先级队列的方式除外还有“时间轮”,这里只介绍优先级队列。
定时器这个东西很重要,非常实用,在后端开发中和“阻塞队列”类似,都会有专门的服务器。
用来在分布式系统中实现定时器这样的效果。
1.要有一个类,描述要执行的任务(任务的时间,内容逻辑)
2.管理多个任务,通过一定数据结构,把多个任务存起来
3.有专门的线程,执行任务
class MyTimerTask implements Comparable<MyTimerTask> {
private Runnable runnable;
// 此处这里的 time, 通过毫秒时间戳, 表示这个任务具体啥时候执行.
private long time;
public MyTimerTask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTimerTask o) {
// 此处这里的 - 的顺序, 就决定了这里是大堆还是小堆.
// 此处需要小堆.
// 这里是谁减谁, 不要背. 可以先写成一种顺序, 试试.
return (int) (this.time - o.time);
// return (int) (o.time - this.time);
}
}
class MyTimer {
private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
private Object locker = new Object();
public MyTimer() {
// 创建线程, 负责执行上述队列中的内容
Thread t = new Thread(() -> {
try {
while (true) {
synchronized (locker) {
while (queue.isEmpty()) {
locker.wait();
}
MyTimerTask current = queue.peek();
// 比如, 当前时间是 10:30, 任务时间是 12:00, 不应该执行.
// 如果当前时间是 10:30, 任务时间是 10:29, 应该执行
if (System.currentTimeMillis() >= current.getTime()) {
// 要执行任务
current.run();
// 把执行过的任务, 从队列中删除.
queue.poll();
} else {
// 先不执行任务
locker.wait(current.getTime() - System.currentTimeMillis());
// Thread.sleep(current.getTime() - System.currentTimeMillis());
}
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
}
public void schedule(Runnable runnable, long delay) {
synchronized (locker) {
MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
queue.offer(myTimerTask);
locker.notify();
}
}
}