JUC--原理篇1理论基础
问题:为什么需要多线程?
–解答:使用多线程的主要目的是提高CPU的执行效率,比如某个正在运行的线程处于等待状态时,就可以让CPU去执行另一个任务,充分利用CPU资源,提高系统性能。
多线程有两种形式:并行和并发,两者的本质区别是并行能够真正做到两个事件同时运行,而并发在宏观上是同时运行,微观上是交替运行。我们主要讨论的是并发。
问题:为什么会出现线程并发安全问题?
–解答:线程并发问题发生在多个线程间共享某个数据,又同时操作该数据的情况。本质上是由以下三个问题造成:可见性、原子性和有序性。
可见性问题指的是某个线程修改了某个变量后还没有写入主存(还在高速缓存中),另一个线程就从主存中读取了该变量的旧数据,并对旧数据进行操作。比如i一开始等于10,线程1将其修改为15,此时15存储在缓存中,此时线程2取出i(10),并将其修改为12,最后i的值可能是12也可能是15.
原子性问题:java中一些操作在底层可能是由多条指令构成,CPU在同一时刻只能执行一条指令,那么就可能有这种情况发生:某个线程的操作执行一半时,CPU被移交给另一个线程,操作的数据又是同一个,最终也得不到 ...
JUC--同步模式
问题:如何保证线程按照某个顺序运行?
问题:如何实现线程的交替执行?
短链接--用户登录以及设置用户信息到上下文
问题:如何保持用户的登录状态?
–解答:可以使用session-cookie机制来维持用户的登录状态,但也可以使用token来保持用户的登录状态,这里使用的是token。其实不管是session,cookie还是token,它们实现鉴权的原理是一样的。就是当你登录成功后,会生成一个凭证,并且将该凭证返回给浏览器,当浏览器向该服务器发送任何请求时都会带上该凭证,而服务器会查看是否有与此凭证对应的物品来判断该用户是否是登录状态。
问题:那这是不是意味着这个物品可以是任意的,比如所有的凭证对应的物品都是同一个?
–解答:理论上是可以这样的,但很多时候我们需要限制凭证的有效时间,如果这样设计的话,就无法实现该要求。因此我们的建议是尽量一个凭证对应一个不同的物品,这样当某个凭证的有效期到了,我们需要删除其对应的物品,基于此,我们还要保证凭证的唯一性,毕竟删除别人的凭证不是一件好事。后面我会解释是如何保证生成的token的唯一性的。你可能会问为什么不用username作为唯一标识符,因为本身我们将这些数据存储进redis,就是因为要在其他地方获得用户数据,这说明其他地方是无法直接获得user ...
Redis--黑马点评项目使用Feed流实现用户推送
问题:如何实现点赞排行榜功能?
问题:Feed流有哪些实现方案?
问题:为什么选用推模式实现用户推送?
问题:推拉结合模式有了解过吗?
问题:为什么使用SortedSet实现用户收件箱?
问题:滚动分页有何难点?如何解决?
JUC--多把锁以及死锁
问题:为什么需要多把锁?
问题:什么是死锁?
问题:如何解决死锁?
有哪些工具定位死锁?
–解答:jstack、jconsole
问题:什么是活锁?如何解决?
问题:如何理解线程的饥饿现象?如何解决?
问题:ReentrantLock有什么特性?相较于Synchronized有什么区别?
可重入、可打断、超时、公平锁、条件变量
JVM学习1
问题:什么是JVM?
问题:为什么需要JVM?
问题:JVM和JDK有什么关系?
问题:什么是字节码文件?
短链接--分组
问题:为什么要分组?如何实现?
–解答:分组可以使用户更好的管理自己的短链接
问题:gid是全局唯一的吗?如何保证?
–解答:gid是全局唯一的。设计方案为:首先使用Hutool的工具生成一个长度为6的随机字符串,然后会去数据库中查询是否已经存在该gid,如果存在则重新生成gid,这个过程会一直循环,直至生成的gid唯一。
问题:分组分表时以什么作为分片件?
–解答:我选择的是username,这样可以保证同一个用户创建的分组在同一个表中。
实现代码:有很多地方写的不是很优雅,有时间再改进
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899/** * 分组服务实现层 */@Servicepublic class GroupServiceImpl e ...
JUC--park和unpark
问题:park和unpark有什么作用?相较于wait和notify有什么优势?
–解答:park和unpark是LockSupport类中提供的两个方法,park()方法可以让正在运行的线程进入等待状态,而unpark()方法可以唤醒等待的线程。
park和unpark不需要获取锁对象后才能使用,每个线程在任何时刻都能调用。相较于notify是随机的唤醒某个线程,unpark可以指定唤醒线程,更加精确
12345678910111213141516171819202122232425262728293031323334package com.deng;import lombok.extern.slf4j.Slf4j;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import java.util.concurrent.locks.LockSupport;import static java.util.concurrent.locks.Lo ...
JUC--设计模式
问题:什么是保护性暂停设计模式?有何作用?
–解答:保护性暂停设计模式是一种维护线程间同步的的方法,比如需要在线程间传递某个数据,线程A只有获得该数据才能继续执行。这时就可以使用保护性暂停,让线程A进入等待状态直到数据到达并被唤醒,线程A继续执行。需要注意的是当线程间有多个数据需要传递时,保护性暂停设计模式无法满足需求,需要使用消息队列进行设计。
具体案例:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758package com.deng;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest@Slf4jpublic class Test2 { public static void main(String[] args) { ...
JUC--wait和notify
问题:wait()和notify()有什么作用?
–解答:这两个方法都是Object中提供的方法,用于线程间的协作交互。wait方法能够将当前占有锁的线程进入等待状态,notify则可以唤醒等待队列中的一个线程
问题:wait()和sleep()有什么区别?
–解答:这两个方法都能够使当前运行的线程进入等待状态(Time_waiting)。不同的是,1. 前者是Object中提供的对象且只有在获取到了锁对象(进入了synchronized代码块)的线程才能使用。而sleep是Thread的静态方法,在任何时候都能够调用该方法;2. 在synchronized代码块中,调用wait方法后线程会释放锁并进入等待状态,而调用sleep方法线程只会进入等待状态不会释放锁。在并发编程中建议使用wait方法,而不要使用sleep方法让线程进入等待状态。
问题:什么是虚假唤醒?如何解决?
–解答:虚假唤醒是指当等待队列中有多个线程处于等待状态时,如果使用notify方法想要唤醒某个线程,可能唤醒的并不是预期线程的情况。可以使用notifayAll方法替代notify方法,该方法会唤醒等待 ...
Redis--黑马点评项目使用Redis实现消息队列异步处理秒杀订单
问题:为什么需要消息队列?
–解答:当前项目中使用java中提供的阻塞队列来存储订单,该阻塞队列有两个问题:1. 内存受限,不能超过机器的内存;2. 一旦机器宕机,位于队列中的信息会全部丢失。而消息队列就是专门用于存放中间信息的,相当于一个中转站,它不受限于机器本身,是一个第三方的组件。
问题:如何使用Redis实现消息队列?为什么不使用List以及PubSub?
–解答:目前市面上有专门的消息队列组件比如:MQ、RabbitMQ、Kafaka等,但其实也可以使用Redis的Stream数据结构实现。
问题:什么情况下会发生Stream消息漏读的情况?如何解决?
–解答:当消费者进程由于某种原因停止运行,那么在这段时间内生产者发送的消息就会被遗漏。可以使用消费者组机制解决,具体来说就是将消费者归为某个组,当消费者出现问题后,恢复时可以选择从pending-list中读取消息,pending-list中存放的是那些未处理的消息。并且采用这种机制后,每个消息处理后消费者需要发送ACK表示已经处理完了该消息,该消息才能从pending-list中移除。
总结一句话:采用消 ...
JUC--synchronized
问题:什么是Monitor?
–解答:Monitor译为监视器,也叫管程。是一个同步工具,基于操作系统中的互斥锁实现。可以抽象为如下结构
问题:synchronized关键字是如何实现锁机制的?
–解答:java对象存储在堆中,具体来说由3部分组成:对象头、对象体和对齐字节。其中对象头中的Mark Word标记字(8字节)用来表示当前线程的锁状态。
1234//临界区synchronized(Object o){ }
最开始,没有线程访问临界区,因此对象o的Mark Word是以下状态(后面会讲到,jvm做了优化,一开始锁对象其实是偏向锁的状态,偏向的是主线程)
表明这是一个正常对象(即无锁状态),此时,来了一个线程并且需要访问临界区,此时锁会升级为偏向锁,对象o的Mark Word的状态发生改变
当一个线程访问临界区代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出临界区时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的线程ID。引入偏向锁是为了在无多线程竞争的情况下尽量减少 ...
