《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!
- 线程抢到了CPU后,可以在很短的时间内运转多次。
这样可能出现只有一个货架,生产者生产完商品后,还没有等消费者来取,就不断的生产新的商品来替代货架上的旧商品的情况。或者消费者已经将商品取走,还在对着空的货架不断取商品的情况。
解决办法:加入等待唤醒机制
等待唤醒机制通过Object类中的三个方法来实现(注意是Object,不是Thread):
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
void notify() 唤醒正在等待对象监视器的单个线程。
void notifyAll() 唤醒正在等待对象监视器的所有线程。
这三个方法的具体使用是:
- wait()
在调用 wait()之前,线程必须要获得相应对象的对象监视器锁(这里是Student对象),所以只能在同步方法或同步块中调用 wait()方法。对象调用wait()方法后,当前线程会立即释放锁,然后进入休眠状态,直到被notify()唤醒或者被中断。同时线程的执行会在wait()语句处停止,直到再次获得锁,当前线程才能从wait()方法处成功返回,然后继续执行下面的代码。另外,被释放的锁会立刻被其他等待锁的线程抢夺,抢到锁的线程开始执行同步代码块。
- notify()
notify()同样需要获得锁,并且只能在同步方法或同步代码块中调用。当前线程调用notify()后,会唤醒之前wait()后陷入休眠的线程,使其从等待队列进入同步队列,获取当前线程的锁,并且从之前wait()语句处继续执行(这里要求对象锁的对象要一致,才会去唤醒)。当等待唤醒的线程较多时,会根据机制随机挑选一个线程唤醒。当前线程调用notify()方法后不会立刻释放锁,而是继续执行,直到执行结束退出同步方法或同步代码块时,才会释放锁。
- notifyAll()
notifyAll()与notify()的工作方式大致相同,不同的是等待线程较多时,notify()会随机挑选一个线程通知,而notifyAll()会将所有具有相同对象锁的线程全部唤醒,让这些线程争抢锁。
这里注意wait()与notify(),notifyAll()联系的桥梁是相同的对象,即synchronized(对象){ },不同的线程之间,括号里面的对象相同
生产者线程
package test.MyThread.ProductDemo;
public class SetThread implements Runnable{
Student s ;
int x = 0;
public SetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized(s){
if(s.flag){
//说明已经给学生对象赋值,应该等待消费者来获取
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//这里是两类商品,当x%2==0的时候上架商品水果,否则上架商品面包
//这里放在学生类里面想当于一个叫水果的学生,一个叫面包的学生
if(x%2==0){
s.setName(“水果”);
s.setAge(18);
}else{
s.setName(“面包”);
s.setAge(15);
}
x++;
//赋值完毕,将状态转为true,并通知消费者来取值
s.flag = true;
s.notify();
}
}
}
}
}
消费者线程
package test.MyThread.ProductDemo;
public class GetThread implements Runnable{
Student s ;
public GetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized(s){
//s.flag为false时,!s,flag为true,运行if语句
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + “—” + s.age);
s.notify();
s.flag = false;
}
}
}
}
s.flag=true说明有商品,s.flag=false说明没有商品
这里没有商品的时候应该用锁对象调用wait()方法,线程释放锁对象,并且代码的执行停留在这一步
锁对象被释放后,会被生产者抢夺,而消费者不会再抢夺锁对象
然后生产者生产商品完毕后,用notify()方法唤醒消费者,并且将锁对象给消费者
调用notify()方法后不会立刻释放锁对象,而是等生产者将同步代码块全部运行完之后才会释放给消费者
这样生产者会继续从头运行,检测是否有商品,如果商品还没有被消费者消费,那么就进入等待状态
最后消费者接着执行代码,消费商品,消费完毕后用notify()方法唤醒生产者生产商品
package test.MyThread.ProductDemo;
public class StudentDemo {
public static void main(String[] args) {
Student s = new Student();
SetThread s1 = new SetThread(s);
GetThread g1 = new GetThread(s);
Thread t1 = new Thread(s1);
Thread t2 = new Thread(g1);
t1.start();
t2.start();
}
}
运行一下,结果为
总目录展示
该笔记共八个节点(由浅入深),分为三大模块。
高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。该笔记将从设计数据的动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化这4个方面重点介绍。
一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。因此,将用一个节点来专门讲解如何设计秒杀减库存方案。
高可用。 虽然介绍了很多极致的优化思路,但现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,还要设计一个PlanB来兜底,以便在最坏情况发生时仍然能够从容应对。笔记的最后,将带你思考可以从哪些环节来设计兜底方案。
篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)
由于内容太多,这里只截取部分的内容。
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!
篇幅有限,无法一个模块一个模块详细的展示(这些要点都收集在了这份《高并发秒杀顶级教程》里),麻烦各位转发一下(可以帮助更多的人看到哟!)
[外链图片转存中…(img-cf6FpA0X-1714433995515)]
[外链图片转存中…(img-fv4huEa6-1714433995515)]
由于内容太多,这里只截取部分的内容。
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!