先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
正文
目录
🌴一、阻塞队列
1.概念
✨对于队列,首先我们想到 队列——先进先出——最朴素,最简单的队列 优先级队列—— PriorityQueue——堆
阻塞队列——带有阻塞特性——先进先出
1.如果队列空,尝试出队列,就会阻塞等待,等待到队列不为空为止
2.如果队列满,尝试入队列,也会阻塞等待,等待到队列不为满为止
在 Java 标准库中内置了阻塞队列
1️⃣BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
2️⃣put 方法用于阻塞式的入队列
3️⃣take 用于阻塞式的出队列
public class ThreadDemo3 {
public static void main(String[] args) throws InterruptedException {
BlockingDeque<String> queue = new LinkedBlockingDeque<>();
//阻塞队列和新方法,主要有两个
//1.put 入队列
queue.put("hello1");
queue.put("hello2");
queue.put("hello3");
queue.put("hello4");
queue.put("hello5");
//2.take 出队列
String result = null;
result = queue.take();
System.out.println(result);
result = queue.take();
System.out.println(result);
result = queue.take();
System.out.println(result);
result = queue.take();
System.out.println(result);
result = queue.take();
System.out.println(result);
result = queue.take();
System.out.println(result);
}
}
结果:上述代码中,put 了5次,take 了6次,前5次 take 都很顺利,第六次 take 就阻塞了
2.生产者消费者模型
编写一个“生产者消费者模型”多线程使用阻塞队列
生产者消费者模型主要解决两个方面的问题:
1️⃣可以让上下游块之间,进行更好的“解耦合”——(耦合——低内聚、高内聚——两个模块之间的关联关系是强还是弱,关联越强,耦合越高)
低内聚——相互关联的代码没有放到一起
高内聚——相关联的代码,分门别类的规制起来,想找很容易
2️⃣ 削峰填谷
public class ThreadDemo4 {
public static void main(String[] args) {
BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();
//消费者
Thread t1 = new Thread(() -> {
while (true) {
try {
int value = blockingDeque.take();
System.out.println("消费元素:" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
//生产者
Thread t2 = new Thread(() -> {
int value = 0;
while (true) {
try {
System.out.println("生产元素:" + value);
blockingDeque.put(value);
value++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
//上述代码,让生产者每隔 1s 生产一个元素
//让消费者直接消费,不受限制
}
}
//生产元素:0
//消费元素:0
//生产元素:1
//消费元素:1
//生产元素:2
//消费元素:2
//生产元素:3
//消费元素:3
//...
3.阻塞队列的实现
❓针对 BlockingQueue 使用恼火的是比较简单的,重点如何实现一个阻塞队列
实现阻塞队列,分三步:
1.先实现一个普通队列
2.加上线程安全
3.加上阻塞功
1️⃣通过 “循环队列” 的方式来实现.
2️⃣使用 synchronized 进行加锁控制.
3️⃣put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一 定队列就不满了, 因为同时可能是唤醒了多个线程).
❓如何区分队列满和队列空?
队列空式,head 和 tail 重合(初始情况);队列满,head 和 tail 也重合
1.浪费一个空间
2.记录元素个数(获取队列中元素的个数,本身就是队列的一个重要的方法)BlockingQueue, 没有实现取队首元素的阻塞版本,只有 put 和 take,虽然也提供 peek 方法,但是这个方法不会阻塞
4️⃣take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
//模拟实现一个阻塞队列
//不写泛型,就直接写朴素的代码,假定存储的元素是 int
//基于数组来实现队列
//记录元素个数
//BlockingQueue,没有实现取队首元素的阻塞版本,只有 put 和 take,虽然也有 peek,但是这个方法不会阻塞
class MyBlockingQueue {
private int[] items = new int[1000];
//约定 [head,tail) 队列的有效元素
volatile private int head = 0;//指向队首元素下标
volatile private int tail = 0;//指向队尾元素下标
volatile private int size = 0;
//入队列
synchronized public void put(int elem) throws InterruptedException {
if (size == items.length) {
//队列满了,插入失败
//return;
this.wait();
}
//把新元素放到 tail 所在位置上
items[tail] = elem;
tail++;
//万一 tail 达到末尾,就需要让 tail 从头再来
if (tail == items.length) {
tail = 0;
}
//tail = tail % items.length;//这个写法也可以达到效果,不推荐,这样写开发效率不好,执行效率也不好
//求余操作不直观,求余对于计算机并不是高效率操作,没有 if 来的快
this.notify();//唤醒出队列
size++;
}
//出队列
synchronized public Integer take() throws InterruptedException {
if (size == 0) {
//return null;
this.wait();
}
int value = items[head];
head++;
if (head == items.length) {
head = 0;
}
size--;
this.notify();//唤醒入队列
return value;
}
}
❗❗注意:上述两个代码的 wait 不可能同时阻塞!!!一个独立不可能即是空,又是满
✨java官方并不建议这么使用 wait,wait可能会导致其他方法给中断的(interrupt 方法),此时 wait 其实等待的条件还没有成熟,就被提前唤醒了,因此代码就可能不符合预期了
if (size == items.length) {
//队列满了,插入失败
//return;
this.wait();
}
❗❗很有可能在别的代码里暗中 interrupt ,把 wait 给提前唤醒了,明明条件还没有满足(队列非空),但是 wait 唤醒之后就继续往下走了
当然,当前代码中,没有 interrupt ,但是一个更复杂的项目,就不能保证没有了,更稳妥的做法,是在 wait 唤醒之后,在判定一次条件wait 之前,发现条件不满足,开始 wait;然后等到 wait 被唤醒了之后,再确认一下条件是不是满足,如果不满足,还可以继续 wait
改为 while 循环:
while (size == items.length) {
//队列满了,插入失败
//return;
this.wait();
}
最终阻塞队列代码的实现:
//模拟实现一个阻塞队列
//不写泛型,就直接写朴素的代码,假定存储的元素是 int
//基于数组来实现队列
//记录元素个数
//BlockingQueue,没有实现取队首元素的阻塞版本,只有 put 和 take,虽然也有 peek,但是这个方法不会阻塞
class MyBlockingQueue {
private int[] items = new int[1000];
//约定 [head,tail) 队列的有效元素
volatile private int head = 0;//指向队首元素下标
volatile private int tail = 0;//指向队尾元素下标
volatile private int size = 0;
//入队列
synchronized public void put(int elem) throws InterruptedException {
while (size == items.length) {
//队列满了,插入失败
//return;
this.wait();
}
//把新元素放到 tail 所在位置上
items[tail] = elem;
tail++;
//万一 tail 达到末尾,就需要让 tail 从头再来
if (tail == items.length) {
tail = 0;
}
//tail = tail % items.length;//这个写法也可以达到效果,不推荐,这样写开发效率不好,执行效率也不好
//求余操作不直观,求余对于计算机并不是高效率操作,没有 if 来的快
this.notify();//唤醒出队列
size++;
}
//出队列
synchronized public Integer take() throws InterruptedException {
if (size == 0) {
//return null;
this.wait();
}
int value = items[head];
head++;
if (head == items.length) {
head = 0;
}
size--;
this.notify();//唤醒入队列
return value;
}
}
//上述两个代码的 wait 不可能同时阻塞!!!一个独立不可能即是空,又是满
public class ThreadDemo1 {
public static void main(String[] args) {
MyBlockingQueue queue = new MyBlockingQueue();
//消费者
Thread t1 = new Thread(() -> {
while (true) {
try {
int value = queue.take();
System.out.println("消费:" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//生产者
Thread t2 = new Thread(() -> {
int value = 0;
while (true) {
try {
System.out.println("生产:" + value);
queue.put(value);
Thread.sleep(1000);
value++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
🏹二、定时器
1.引出定时器
定时器:设定一个时间,当时间到,就可以执行一个指定的代码
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
},2000);
标准库中的定时器
标准库中提供了一个
Timer
类. Timer 类的核心方法为
schedule .schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello4");
}
},4000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello3");
}
},3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello2");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello1");
}
},1000);
System.out.println("hello0");
}
此时发现这个代码打印:
hello0
hello1
hello2
hello3
hello4并且这个代码没有结束,是因为 Timer 里边内置了线程(还是前台线程),会阻止进程结束
2.定时器的实现
1.定时器的构成: 一个带优先级的阻塞队列
为啥要带优先级呢?
因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带
优先级的队列就可以高效的把这个 delay 最小的任务找出来.
1️⃣Timer 类提供的核心接口为 schedule, 用于注册一个任务, 并指定这个任务多长时间后执行.
public class Timer {
public void schedule(Runnable runnable, long delay) {
}
}
2️⃣Task 类用于描述一个任务(作为 Timer 的内部类). 里面包含一个 Runnable 对象和一个 time (毫秒时间戳)
这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.
//表示一个任务
class MyTask implements Comparable<MyTask>{
public Runnable runnable;
//为了方便后续判断,使用绝对的时间戳
public long time;
public MyTask(Runnable runnable,long delay) {
this.runnable = runnable;
//取当前时间的时间戳 + delay,作为读任务实际执行的时间戳
this.time = System.currentTimeMillis() + delay;
}
@Override
public int compareTo(MyTask o) {
//这样写意味着每次取出的是时间最小的元素
return (int)(this.time - o.time);
}
}
3️⃣Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象. 通过 schedule 来往队列中插入一个个 Task 对象.
class MyTimer {
//带有优先级的阻塞队列,核心数据结构
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//此处的 delay 是一个形如 3000 这样的数字(多长时间之后,执行读任务)
public void schedule(Runnable runnable, long delay) {
//根据参数,构造 MyTask,插入队列即可
MyTask myTask = new MyTask(runnable, delay);
queue.put(myTask);//入队列
}
}
4️⃣Timer 类中存在一个 myTask 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务.
class MyTimer {
//带有优先级的阻塞队列,核心数据结构
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//此处的 delay 是一个形如 3000 这样的数字(多长时间之后,执行读任务)
public void schedule(Runnable runnable, long delay) {
//根据参数,构造 MyTask,插入队列即可
MyTask myTask = new MyTask(runnable, delay);
queue.put(myTask);//入队列
}
//在这里构造线程,负责执行具体任务
public MyTimer() {
Thread t = new Thread(() -> {
while (true) {
try {
//阻塞队列,只有阻塞入队列和阻塞出队列,没有阻塞的查看队首元素,因此出队列之后时间没到就需要重新入队列
MyTask myTask = queue.take();//出队列//去队列中取元素 1.当前队列为空,take直接阻塞,2.队列有元素,直接获取到
long curTime = System.currentTimeMillis();//获取系统时间
if (myTask.time <= curTime) {
//时间到了,可以执行任务
myTask.runnable.run();
} else {
//时间还没到,就把刚才取出来的任务,重新塞回到队列中
queue.put(myTask);//时间没到,入队列
//时间没到需要加入一个等待,避免忙等 不适合用sleep,不方便唤醒,使用 wait,可以随时唤醒
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
5️⃣引入一个 locker 对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
//时间还没到,就把刚才取出来的任务,重新塞回到队列中
queue.put(myTask);//时间没到,入队列
//时间没到需要加入一个等待,避免忙等 不适合用sleep,不方便唤醒,使用 wait,可以随时唤醒
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
5️⃣**引入一个 locker 对象, 借助该对象的 wait / notify 来解决 while (true) 的忙等问题.**
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-yCLzWMG1-1713343408099)]
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**