大厂面试第三季
1.java基础
1.1 58同城的java字符串常量池
面试题code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.hhf.study.javase;
public class StringPool58Demo { public static void main(String[] args) {
String str1 = new StringBuilder("58").append("tongcheng").toString(); System.out.println(str1); System.out.println(str1.intern()); System.out.println(str1 == str1.intern());
System.out.println("------------");
String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2); System.out.println(str2.intern()); System.out.println(str2 == str2.intern()); } }
|
讲解
intern()方法

按照代码结果,java字符串答案为false 必然是两个不同的java,那另外一个java字符串如何加载进来的?
有一个初始化的java字符串(JDK出娘胎自带的), 在加载sun.misc.Version这个类的时候进入常量池
OpenJDK8底层源码说明
System代码解析 initializeSystemClass


根加载器提前部署加载rt.jar

总结
这段代码在JDK 6中运行,会得到两个false,而在JDK 7中运行,会得到一个true和一个false。产生差异的原因是,在JDK 6中,intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用,而由StringBuilder创建的字符串对象实例在Java堆上,所以必然不可能是同一个引用,结果将返回false。
而JDK 7(以及部分其他虚拟机,例如JRockit)的intern()方法实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可,因此intern()返回的引用和由StringBuilder创建的那个字符串实例就是同一个。而对str2比较返回false,这是因为”java”这个字符串在执行String-Builder.toString()之前就已经出现过了,字符串常量池中已经有它的引用,不符合intern()方法要求“首次遇到”的原则,“计算机软件”这个字符串则是首次出现的,因此结果返回true。
sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化,而在初始化时它需要对静态常量字段根据指定的常量值(ConstantValue)做默认初始化,此时被sun.misc.Version.launcher静态常量字段所引用的”java”字符串字面量就被intern到HotSpot VM的字符串常量池——StringTable里了。
1.2 字节跳动两数求和
力扣第一题:两数之和
https://leetcode-cn.com/problems/two-sum/

暴力破解法和 哈希(更优解法)
暴力破解法通过双重循环遍历数组中所有元素的两两组合,当出现符合的和时返回两个元素的下标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
public class TwoSumDemo { public static void main(String[] args) { int[]nums ={2, 7, 11, 15}; int target = 99; int[]indexCollection=twoSum2(nums,target); if(indexCollection!=null){ for (int index : indexCollection) { System.out.print(index+" "); } } }
public static int[] twoSum1(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { for (int j = i+1; j <nums.length ; j++) { if(target-nums[i]==nums[j]){ return new int[]{i,j}; } } } return null; } public static int[] twoSum2(int[] nums, int target){ Map<Integer,Integer> map=new HashMap<>(); for (int i = 0; i < nums.length; i++) { int param=target-nums[i]; if(map.containsKey(param)){ return new int[]{map.get(param),i}; } map.put(nums[i],i); } return null; } }
|
2.JUC
2.1 大厂面试题复盘
Synchronized相关问题
- Synchronized用过吗,其原理是什么?
- 你刚才提到获取对象的锁。这个“锁”到底是什么?如何确定对象的锁?
- 什么是可重入性.为什么说Synchronized是可重入锁?
- JMM对Java的原生锁做了哪些优化?
- 为什么说Synchronized是非公平锁?
- 什么是锁消除和锁粗化?
- 为什么说Synchronized是一个悲观锁?乐观锁的实现原理又是什么?什么是CAS.
- 乐观锁一定就是好的吗?
可重入锁ReentrantLock及其他显式锁相关问题
- 跟Synchronized相比,可重入锁ReentrantLock其实现原理有什么不同?
- 那么请谈谈AQS框架是怎么回事儿?
- 请尽可能详尽地对比下Synchronized和ReentrantLock的异同。
- ReentrantLock是如何实现可重入性的?
2.2 可重入锁
说明
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),
不会因为之前已经获取过还没释放而阻塞。
Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。
自己可以获取自己的内部锁
可重入锁种类
隐式锁(即synchronized关键字使用的锁)默认是可重入锁
同步块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.hhf.study.juc;
public class ReEnterLockDemo { static Object objectLockA = new Object(); public static void m1(){ new Thread(() -> { synchronized (objectLockA){ System.out.println(Thread.currentThread().getName()+"\t"+"------外层调用"); synchronized (objectLockA){ System.out.println(Thread.currentThread().getName()+"\t"+"------中层调用"); synchronized (objectLockA) { System.out.println(Thread.currentThread().getName()+"\t"+"------内层调用"); } } } },"t1").start(); } public static void main(String[] args) { m1(); } }
|
同步方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.hhf.study.juc;
public class ReEnterLockDemo { public synchronized void m1(){ System.out.println("=====外层"); m2(); } public synchronized void m2() { System.out.println("=====中层"); m3(); } public synchronized void m3(){ System.out.println("=====内层"); } public static void main(String[] args) { new ReEnterLockDemo().m1(); } }
|
Synchronized的重入的实现机理
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter时,如果目标锋对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设
置为当前线程,并且将其计数器加i。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待
,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
显式锁(即Lock)也有ReentrantLock这样的可重入锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package com.hhf.study.juc;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class ReEnterLockDemo {
static Lock lock = new ReentrantLock();
public static void main(String[] args) { new Thread(() -> { lock.lock(); try{ System.out.println("=======外层"); lock.lock(); try{ System.out.println("=======内层"); }finally { lock.unlock(); } } finally { lock.unlock(); } },"t1").start();
new Thread(() -> { lock.lock(); try{ System.out.println("b thread----外层调用lock"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } },"b").start(); } }
|
2.3 LockSupport
LockSupport是什么

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
下面这句话,后面详细说
LockSupport中的park()和unpark()的作用分别是阻塞线程和解除阻塞线程
从阿里蚂蚁金服面试题讲起
InterruptedException你说说

线程等待唤醒机制(wait/notify)
3种让线程等待和唤醒的方法
方式1: 使用Object中的wait()方法让线程等待, 使用Object中的notify()方法唤醒线程
方式2: 使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
方式3: LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
Object类中的wait和notify方法实现线程等待和唤醒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class LockSupportDemo {
static Object objectLock = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (objectLock){ System.out.println(Thread.currentThread().getName()+"\t"+"------come in"); try { objectLock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t"+"------被唤醒"); } },"A").start();
new Thread(() -> { synchronized (objectLock) { objectLock.notify(); System.out.println(Thread.currentThread().getName()+"\t"+"------通知"); } },"B").start(); } }
|

wait方法和notify方法,两个都去掉同步代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.zzxx.study.juc;
|

将notify放在wait方法前面 程序无法执行,无法唤醒
1 2 3 4 5 6 7 8 9 10 11
| package com.zzxx.study.juc;
|

ait和notify方法必须要在同步块或者方法里面且成对出现使用
先wait后notify才OK
Condition接口中的await后signal方法实现线程的等待和唤醒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.zzxx.study.juc;
public class LockSupportDemo { static Lock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) {
new Thread(() -> { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t" + "------come in"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + "------被唤醒"); } finally { lock.unlock(); } }, "A").start();
new Thread(() -> { lock.lock(); try { condition.signal(); System.out.println(Thread.currentThread().getName() + "\t" + "------通知"); } finally { lock.unlock(); } }, "B").start(); } }
|



传统的synchronized和Lock实现等待唤醒通知的约束
LockSupport类中的park等待和unpark唤醒
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),
permit只有两个值1和零,默认是零。
可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。

阻塞
park()/park(Object blocker)

permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,
然后会将permit再次设置为0并返回。
阻塞当前线程/阻塞传入的具体线程
唤醒
unpark(Thread thread)

调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,
即之前阻塞中的LockSupport.park()方法会立即返回。
唤醒处于阻断状态的指定线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public class LockSupportDemo {
public static void main(String[] args) {
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t ----come in"); LockSupport.park(); System.out.println(Thread.currentThread().getName() + "\t ----被唤醒"); }, "a"); a.start();
Thread b = new Thread(() -> {
LockSupport.unpark(a); System.out.println(Thread.currentThread().getName() + "\t ----通知了"); }, "b"); b.start(); } }
|
之前错误的先唤醒后等待,LockSupport照样支持

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根
结底,LockSupport调用的Unsafe中的native代码。
LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,
调用一次unpark就加1变成1,
调用一次park会消费permit,也就是将1变成o,同时park立即返回。
如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。
每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
当调用park方法时
- 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
- 如果无凭证,就必须阻塞等待凭证可用;
而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;
而调用两次park却需要消费两个凭证,证不够,不能放行。
2.4 AbstractQueuedSynchronizer之AQS

4.Spring
4.1 spring的aop顺序
Aop常用注解
- @Before 前置通知: 目标方法之前执行
- @After 后置通知: 目标方法之后执行(始终执行)
- @AfterReturning 返回后通知: 执行方法结束前执行(异常不执行)
- @AfterThrowing 异常通知: 出现异常时候执行
- @Around 环绕通知: 环绕目标方法执行
面试题
- 你肯定知道spring,那说说aop的全部通知顺序
- springboot或springboot2对aop的执行顺序影响?
- 说说你使用aop中碰到的坑
业务类
想在除法方法前后各种通知,引入切面编程
1 2 3 4 5 6 7 8
| package com.hhf.study.spring.aop;
public interface CalcService {
public int div(int x,int y);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.hhf.study.spring.aop;
import org.springframework.stereotype.Service;
@Service public class CalcServiceImpl implements CalcService {
@Override public int div(int x, int y) { int result = x / y; System.out.println("=========>CalcServiceImpl被调用了,我们的计算结果:"+result); return result;
} }
|
新建一个切面类MyAspect并为切面类新增两个注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Aspect @Component public class MyAspect { @Before("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))") public void beforeNotify() { System.out.println("******** @Before我是前置通知MyAspect"); } @After("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))") public void afterNotify() { System.out.println("******** @After我是后置通知"); } @AfterReturning("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))") public void afterReturningNotify() { System.out.println("********@AfterReturning我是返回后通知"); } @AfterThrowing("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))") public void afterThrowingNotify() { System.out.println("********@AfterThrowing我是异常通知"); } @Around("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object retValue = null; System.out.println("我是环绕通知之前AAA"); retValue = proceedingJoinPoint.proceed(); System.out.println("我是环绕通知之后BBB"); return retValue; } }
|
Spring4+springboot1.5.9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version> <relativePath/> </parent> <modelVersion>4.0.0</modelVersion>
<groupId>com.hhf</groupId> <artifactId>interview1024</artifactId> <version>0.0.1-SNAPSHOT</version>
<properties> <java.version>1.8</java.version> </properties>
<dependencies>
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-captcha</artifactId> <version>4.6.8</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
|
正常

异常

- @Before( 前置通知)=====>@After (后置通知)=====>@AfterReturning(正常返回)
- @Before( 前置通知)=====>@After (后置通知)=====>@AfterReturning(方法异常)
spring4默认用的是JDK的动态代理
Spring5+springboot2.3.3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.3.RELEASE</version>
<relativePath/> </parent> <modelVersion>4.0.0</modelVersion>
<groupId>com.hhf</groupId> <artifactId>interview1024</artifactId> <version>0.0.1-SNAPSHOT</version>
<properties> <java.version>1.8</java.version> </properties>
<dependencies>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-captcha</artifactId> <version>4.6.8</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.hhf.study;
import com.hhf.study.service.CalcService; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootVersion; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.SpringVersion; import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest public class T1 { @Autowired private CalcService service; @Test public void testAop4(){ System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+ SpringBootVersion.getVersion()); System.out.println(); calcService.div(10,2); } }
|
正常

异常

- @Before( 前置通知)=====>@AfterReturning(正常返回)=====>@After (后置通知)
- @Before( 前置通知)=====>@AfterReturning(方法异常)=====>@After (后置通知)
- @After 就类似于 try catch finally 中的 finally
结论

4.2 spring的循环依赖
大厂面试题
- 你解释下spring中的三级缓存?
- 三级缓存分别是什么?三个Map有什么异同?
- 什么是循环依赖?请你谈谈?看过spring源码吗?一般我们说的spring容器是什么
- 如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
- 多例的情况下,循环依赖问题为什么无法解决?
什么是循环依赖
- 多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A

- 通常来说,如果问spring容器内部如何解决循环依赖, 一定是指默认的单例Bean中,属性互相引用的场景

- 也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题
两种注入方式对循环依赖的影响

- 我们AB循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题
- 如果是构造注入会有循环依赖问题
spring容器循环依赖报错演示BeanCurrentlylnCreationException
循环依赖现象在Spring容器中 注入依赖的对象,有2种情况
构造器方式注入依赖
1 2 3 4 5 6 7 8 9 10 11
| import org.springframework.stereotype.Component;
@Component public class ServiceA {
private ServiceB serviceB;
public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; } }
|
1 2 3 4 5 6 7 8 9 10 11
| import org.springframework.stereotype.Component;
@Component public class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public class ClientConstructor { public static void main(String[] args) { new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); .... } }
|
构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的

以set方式注入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import org.springframework.stereotype.Component;
@Component public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; System.out.println("A 里面设置了B"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| import org.springframework.stereotype.Component;
@Component public class ServiceB {
private ServiceA serviceA;
public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; System.out.println("B 里面设置了A"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class ClientSet { public static void main(String[] args) {
ServiceA serviceA = new ServiceA();
ServiceB serviceB = new ServiceB();
serviceB.setServiceA(serviceA);
serviceA.setServiceB(serviceB);
} }
|
重要code案例演示
code-java基础编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public class A { private B b;
public B getB(){ return b; }
public void setB(B b){ this.b = b; }
public A(){ System.out.println("---A created success"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class B { private A a;
public A getA(){ return a; }
public void setA(A a){ this.a = a; }
public B(){ System.out.println("---B created success"); } }
|
1 2 3 4 5 6 7 8 9
| public class ClientCode { public static void main(String[] args) { A a = new A(); B b = new B();
a.setB(b); b.setA(a); } }
|
spring容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="a" class="com.hhf.study.spring.circulardepend.A" > <property name="b" ref="b"/> </bean>
<bean id="b" class="com.hhf.study.spring.circulardepend.B"> <property name="a" ref="a"/> </bean>
</beans>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| log4j.rootLogger = debug,stdout,D,E
log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File = E://logs/log.log log4j.appender.D.Append = true log4j.appender.D.Threshold = DEBUG log4j.appender.D.layout = org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender log4j.appender.E.File =E://logs/error.log log4j.appender.E.Append = true log4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayout log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ClientSpringContainer { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); A a = context.getBean("a",A.class); B b = context.getBean("b",B.class); } }
|

重要结论(spring内部通过3级缓存来解决循环依赖)
- DefaultSingletonBeanRegistry
只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
第三级缓存: Map<String, ObiectFactory<?>> singletonFactories,存放可以生成Bean的工厂

所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map
循环依赖Debug(困难)
实例化/初始化
- 实例化
- 堆内存中申请一块内存空间
- 租赁好房子,自己的家具东西还没有搬家进去
- 初始化属性填充
3大Map和四大方法,总体相关对象

- 1.getSingleton:希望从容器里面获得单例的bean,没有的话
- 2.doCreateBean: 没有就创建bean
- 3.populateBean: 创建完了以后,要填充属性
- 4.addSingleton: 填充完了以后,再添加到容器进行使用
第一层singletonObjects存放的是已经初始化好了的Bean,
第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

A/B两对象在三级缓存中的迁移说明
1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。


全部Debug断点

总结spring是如何解决的循环依赖?
- Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化
- 每次创建bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个
- 当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完 beanB填充属性时又发现它依赖了beanA又是同样的流程,不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入beanB,完成beanB的创建
- 既然 beanB创建好了,所以beanA就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成

Spring解决循环依赖依靠的是Bean的“中间态”这个概念,而这个中间态指的是已经实例化但还没初始化的状态……>半成品。
实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
Spring为了解决单例的循环依赖问题,使用了三级缓存其中
- 一级缓存为单例池〈 singletonObjects)
- 二级缓存为提前曝光对象( earlySingletonObjects)
- 三级缓存为提前曝光对象工厂( singletonFactories)。
假设A、B循环引用,实例化A的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖A,这时候从缓存中查找到早期暴露的A,没有AOP代理的话,直接将A的原始对象注入B,完成B的初始化后,进行属性填充和初始化,这时候B完成后,就去完成剩下的A的步骤,如果有AOP代理,就进行AOP处理获取代理后的对象A,注入B,走剩下的流程。
spring解决循环依赖的整个流程图

Debug的步骤—->Spring解决循环依赖过程
- 1 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
- 2 在getSingleton()方法中,从一级缓存中查找,没有,返回null
- 3 doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
- 4 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法
- 5 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
- 6 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
- 7 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
- 8 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA
- 9 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
- 10 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中
5.Redis
5.1 安装redis6.0.8


5.2 redis传统五大基本类型的落地应用
8大类型
- 1.String(字符类型)
- 2.Hash(散列类型)
- 3.List(列表类型)
- 4.Set(集合类型)
- 5.SortedSet(有序集合类型,简称zset)
- 6.Bitmap(位图)
- 7.HyperLogLog(统计)
- 8.GEO(地理)
备注
- 命令不区分大小写,而key是区分大小写的
- help @类型名词
String
最常用
同时设置/获取多个键值
- MSET key value [key value ….]
- MGET key [key ….]
数值增减
- 递增数字 INCR key
- 增加指定的整数 INCRBY key increment
- 递减数值 DECR key
- 减少指定的整数 DECRBY key decrement
获取字符串长度
分布式锁
- setnx key value
- set key value [Ex seconds][PX milliseconds][NX|XX]

应用场景
商品编号、订单号采用INCR命令生成

hash
- Map<String,Map<Object,object>>
一次设置一个字段值
一次获取一个字段值
一次设置多个字段值
- HMSET key field value [field value …]
一次获取多个字段值
- HMGETkey field [field ….]
获取所有字段值
获取某个key内的全部数量
删除一个key
应用场景
购物车早期,当前小中厂可用

list
向列表左边添加元素
- LPUSH key value [value …]
向列表右边添加元素
- RPUSH key value [value ….]
查看列表
获取列表中元素的个数
应用场景
微信文章订阅公众号


set
添加元素
- SADD key member[member …]
删除元素
- SREM key member [member …]
获取集合中的所有元素
判断元素是否在集合中
获取集合中的元素个数
从集合中随机弹出一个元素,元素不删除
从集合中随机弹出一个元素,出一个删一个
集合运算
- 集合的差集运算A-B
- 属于A但不属于B的元素构成的集合
- SDIFF key [key …]
- 集合的交集运算A∩B
- 属于A同时也属于B的共同拥有的元素构成的集合
- SINTER key [key …]
- 集合的并集运算AUB
- 属于A或者属于B的元素合并后的集合
- SUNION key [key …]
应用场景
|
|
| 1 用户ID,立即参与按钮 |
sadd key 用户ID |
| 2 显示已经有多少人参与了,上图23208人参加 |
SCARD key |
| 3 抽奖(从set中任意选取N个中奖人) |
SRANDMEMBER key 2 随机抽奖2个人,元素不删除 SPOP key3 随机抽奖3个人,元素会删除 |
微信朋友圈点赞

微博好友关注社交关系
QQ内推可能认识的人
zset
向有序集合中加入一个元素和该元素的分数
添加元素
- ZADD key score member [score member …]
按照元素分数从小到大的顺序 返回索引从start到stop之间的所有元素
- ZRANGE key start stop [WITHSCORES]
获取元素的分数
删除元素
- ZREM key member [member …]
获取指定分数范围的元素
- ZRANGEBYsCORE key min max [WITHSCORES] [LIMIT offset count]
增加某个元素的分数
- ZINCRBY key increment member
获取集合中元素的数量
获得指定分数范围内的元素个数
按照排名范围删除元素
- ZREMRANGEBYRANK key start stop
获取元素的排名
应用场景
思路:定义商品销售排行榜(sorted set集合),key为goods:sellsort,分数为商品销售数量。
|
|
| 商品编号1001的销量是9,商品编号1002的销量是15 |
zadd goods:sellsort 9 1001 15 1002 |
| 有一个客户又买了2件商品1001,商品编号1001销量加2 |
zincrby goods:sellsort 2 1001 |
| 求商品销量前10名 |
ZRANGE goods:sellsort 0 10 withscores |

5.3 知道分布式锁吗?
- 有哪些实现方案? 你谈谈对redis分布式锁的理解, 删key的时候有什么问题?
分布式锁的面试题
- Redis除了拿来做缓存,你还见过基于Redis的什么用法?
- Redis做分布式锁的时候有需要注意的问题?
- 如果是Redis是单点部署的,会带来什么问题?
- 集群模式下,比如主从模式,有没有什么问题呢?
- 那你简单的介绍一下Redlock吧?你简历上写redisson,你谈谈
- Redis分布式锁如何续期?看门狗知道吗?
Base案例(boot+redis)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.3.RELEASE</version> <relativePath/> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.hhf</groupId> <artifactId>boot_redis01</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server.port=1111
spring.redis.database=0 spring.redis.host= spring.redis.port=6379
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.hhf.study;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class BootRedis01Application { public static void main(String[] args) { SpringApplication.run(BootRedis01Application.class); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.hhf.study.config;
import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;
@Configuration public class RedisConfig {
@Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){ RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class GoodController {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){
String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; }
}
|

方案一 单机版没加锁
问题
没有加锁,并发下数字不对,出现超卖现象
解决
在单机环境下,可以使用synchronized或Lock来实现。
但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),所以需要一个让所有进程都能访问到的锁来实现,比如redis或者zookeeper来构建;
不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程
加synchronized
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){ synchronized (this) { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
private final Lock lock = new ReentrantLock();
@GetMapping("/buy_goods") public String buy_Goods(){
if (lock.tryLock()){ try { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result); if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; } }finally { lock.unlock(); } }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; }
}
|
方案二 nginx分布式微服务架构
问题
分布式部署后,单机锁还是出现超卖现象,需要分布式锁
Nginx配置负载均衡

./nginx -s reload
/usr/local/nginx/sbin/nginx-c /usr/local/nginx/conf/nginx.conf
./nginx-c /usr/local/nginx/conf/nginx.conf
解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName(); Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);
if (!lockFlag) { return "抢锁失败,┭┮﹏┭┮"; }else { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); stringRedisTemplate.delete(REDIS_LOCK_KEY); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } } }
|
方案三
问题
出异常的话,可能无法释放锁, 必须要在代码层面finally释放锁
解决
- 加锁解锁,lock/unlock必须同时出现并保证调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName(); try{ Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);
if (!lockFlag) { return "抢锁失败,┭┮﹏┭┮"; }else { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } }finally { stringRedisTemplate.delete(REDIS_LOCK_KEY); } } }
|
方案四
问题
部署了微服务jar包的机器挂了,代码层面根本没有走到finally这块, 没办法保证解锁,这个key没有被删除,需要加入一个过期时间限定key
解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName(); try{ Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value); stringRedisTemplate.expire(REDIS_LOCK_KEY,10L, TimeUnit.SECONDS); if (!lockFlag) { return "抢锁失败,┭┮﹏┭┮"; }else { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } }finally { stringRedisTemplate.delete(REDIS_LOCK_KEY); } } }
|
方案五
问题
设置key+过期时间分开了,必须要合并成一行具备原子性
解决
- setIfAbsent(REDIS_LOCK_KEY, value,10L, TimeUnit.SECONDS);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName(); try{ Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value,10L, TimeUnit.SECONDS);
if (!lockFlag) { return "抢锁失败,┭┮﹏┭┮"; }else { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } }finally { stringRedisTemplate.delete(REDIS_LOCK_KEY); } } }
|
方案六
问题
张冠李戴,删除了别人的锁

解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName(); try{ Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value,10L, TimeUnit.SECONDS); stringRedisTemplate.expire(REDIS_LOCK_KEY,10L, TimeUnit.SECONDS); if (!lockFlag) { return "抢锁失败,┭┮﹏┭┮"; }else { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } }finally { if (value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))){ stringRedisTemplate.delete(REDIS_LOCK_KEY); } }
} }
|
方案七
问题
finally块的判断+del删除操作不是原子性的
解决

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| package com.hhf.study.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
try{ Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value,10L, TimeUnit.SECONDS); stringRedisTemplate.expire(REDIS_LOCK_KEY,10L, TimeUnit.SECONDS); if (!lockFlag) { return "抢锁失败,┭┮﹏┭┮"; }else { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } }finally { while (true) { stringRedisTemplate.watch(REDIS_LOCK_KEY); if (value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))){ stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi(); stringRedisTemplate.delete(REDIS_LOCK_KEY); List<Object> list = stringRedisTemplate.exec(); if (list == null) { continue; } } stringRedisTemplate.unwatch(); break; } } } }
|
用Lua脚本
- Redis可以通过eval命令保证代码执行的原子性
RedisUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.hhf.study.util;
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig;
public class RedisUtils {
private static JedisPool jedisPool;
static { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(20); jedisPoolConfig.setMaxIdle(10); jedisPool = new JedisPool(jedisPoolConfig,"ip",6379,100000); }
public static Jedis getJedis() throws Exception{ if (null!=jedisPool){ return jedisPool.getResource(); } throw new Exception("Jedispool is not ok"); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package com.hhf.study.controller;
import com.hhf.study.util.RedisUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import redis.clients.jedis.Jedis;
import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@GetMapping("/buy_goods") public String buy_Goods() throws Exception{
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
try{ Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value,10L, TimeUnit.SECONDS); stringRedisTemplate.expire(REDIS_LOCK_KEY,10L, TimeUnit.SECONDS); if (!lockFlag) { return "抢锁失败,┭┮﹏┭┮"; }else { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; } }finally { Jedis jedis = RedisUtils.getJedis();
String script = "if redis.call('get', KEYS[1]) == ARGV[1]"+"then " +"return redis.call('del', KEYS[1])"+"else "+ " return 0 " + "end"; try{ Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(value)); if ("1".equals(result.toString())){ System.out.println("------del REDIS_LOCK_KEY success"); }else { System.out.println("------del REDIS_LOCK_KEY error"); } }finally { if (null != jedis){ jedis.close(); } } }
} }
|
方案八
问题
- 确保redisLock过期时间大于业务执行时间的问题
- redis异步复制造成的锁丢失, 比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了。
方案九
- redis集群环境下,我们自己写的也不OK, 直接上RedLock之Redisson落地实现
解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.hhf.study.config;
import org.redisson.Redisson; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;
@Configuration public class RedisConfig {
@Value("${spring.redis.host}") private String redisHost;
@Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){ RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; }
@Bean public Redisson redisson(){ Config config = new Config(); config.useSingleServer().setAddress("redis://"+redisHost+":6379").setDatabase(0); return (Redisson) Redisson.create(config); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package com.hhf.study.controller;
import org.redisson.Redisson; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import redis.clients.jedis.Jedis;
import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@Autowired private Redisson redisson;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY); redissonLock.lock(); try{ String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort;
}finally { redissonLock.unlock(); } } }
|
异常

出现这个错误的原因是在并发多的时候就可能会遇到这种错误,可能会被重新抢占
不见得当前这个锁的状态还是在锁定,并且本线程持有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| package com.hhf.study.controller;
import org.redisson.Redisson; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import redis.clients.jedis.Jedis;
import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
@RestController public class GoodController {
public static final String REDIS_LOCK_KEY = "lockhhf";
@Autowired private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}") private String serverPort;
@Autowired private Redisson redisson;
@GetMapping("/buy_goods") public String buy_Goods(){
String value = UUID.randomUUID().toString()+Thread.currentThread().getName();
RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY); redissonLock.lock(); try{ String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort;
}finally { if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){ redissonLock.unlock(); }
} } }
|
5.4 redis缓存过期淘汰策略
粉丝反馈的面试题
- 生产上你们的redis内存设置多少?
- 如何配置、修改redis的内存大小
- 如果内存满了你怎么办
- redis清理内存的方式?定期删除和惰性删除了解过吗
- redis缓存淘汰策略
- redis的LRu了解过吗?可否手写一个LRu算法
Redis内存满了怎么办
redis默认内存多少?在哪里查看? 如何设置修改?
一般推荐Redis设置内存为最大物理内存的四分之三,也就是0.75

如果不设置最大内存大小或者设置最大内存大小为0,|在64位操作系统下不限制内存大小,|在32位操作系统下最多使用3GB内存
一般推荐Redis设置内存为最大物理内存的四分之三,也就是0.75
通过修改文件配置

通过命令修改

如果Redis内存使用超出了设置的最大值会怎样?

info memory
结论
- 设置了maxmemory的选项,假如redis内存使用达到上限
- 没有加上过期时间就会导致数据写满maxmemory 为了避免类似情况,引出下一章内存淘汰策略
redis缓存淘汰策略
有哪些(redis6.0.8版本)
- noeviction: 不会驱逐任何key
- allkeys-lru: 对所有key使用LRU算法进行删除
- volatile-lru: 对所有设置了过期时间的key使用LRU算法进行删除
- allkeys-random: 对所有key随机删除
- volatile-random: 对所有设置了过期时间的key随机删除
- volatile-ttl: 删除马上要过期的key
- allkeys-lfu: 对所有key使用LFU算法进行删除
- volatile-lfu: 对所有设置了过期时间的key使用LFU算法进行删除
总结
如何配置、修改
config set maxmemory-policy allkeys-lru


5.5 redis的LRU算法简介
- redis的LRU了解过吗? 可否手写一个LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的数据予以淘汰。
算法来源
https://leetcode-cn.com/problems/lru-cache/

设计思想
查找快,插入快,删除快,且还需要先后排序——–>什么样的数据结构满足这个问题?
你是否可以在O(1)时间复杂度内完成这两种操作?
如果一次就可以找到,你觉得什么数据结构最合适??
- LRU的算法核心是哈希链表
- 本质就是HashMap+DoubleLinkedList 时间复杂度是O(1),哈希表+双向链表的结合体
编码手写如何实现LRU
案例01
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.hhf.study.lru;
import java.util.LinkedHashMap; import java.util.Map;
public class LRUCacheDemo<K,V> extends LinkedHashMap<K, V> {
private int capacity;
public LRUCacheDemo(int capacity) { super(capacity,0.75F,false); this.capacity = capacity; }
@Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return super.size() > capacity; }
public static void main(String[] args) { LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);
lruCacheDemo.put(1,"a"); lruCacheDemo.put(2,"b"); lruCacheDemo.put(3,"c"); System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(4,"d"); System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(3,"c"); System.out.println(lruCacheDemo.keySet()); lruCacheDemo.put(3,"c"); System.out.println(lruCacheDemo.keySet()); lruCacheDemo.put(3,"c"); System.out.println(lruCacheDemo.keySet()); lruCacheDemo.put(5,"x"); System.out.println(lruCacheDemo.keySet()); } }
|
案例02
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
| package com.lrm.web;
import java.util.HashMap; import java.util.Map;
public class LRUCacheDemo{
class Node<K, V> { K key; V value; Node<K,V> prev; Node<K,V> next;
public Node(){ this.prev = this.next = null; }
public Node(K key, V value) { this.key = key; this.value = value; this.prev = this.next = null; }
}
class DoubleLinkedList<K, V> { Node<K, V> head; Node<K, V> tail;
public DoubleLinkedList(){ head = new Node<>(); tail = new Node<>(); head.next = tail; tail.prev = head; }
public void addHead(Node<K,V> node) { node.next = head.next; node.prev = head; head.next.prev = node; head.next = node; }
public void removeNode(Node<K, V> node) { node.next.prev = node.prev; node.prev.next = node.next; node.prev = null; node.next = null; }
public Node getLast() { return tail.prev; } }
private int cacheSize; Map<Integer,Node<Integer,Integer>> map; DoubleLinkedList<Integer,Integer> doubleLinkedList;
public LRUCacheDemo(int cacheSize) { this.cacheSize = cacheSize; map = new HashMap<>(); doubleLinkedList = new DoubleLinkedList<>(); }
public int get(int key){ if (!map.containsKey(key)){ return -1; }
Node<Integer, Integer> node = map.get(key); doubleLinkedList.removeNode(node); doubleLinkedList.addHead(node);
return node.value; }
public void put(int key, int value) { if (map.containsKey(key)){ Node<Integer, Integer> node = map.get(key); node.value = value; map.put(key, node);
doubleLinkedList.removeNode(node); doubleLinkedList.addHead(node); }else { if (map.size() == cacheSize) { Node<Integer,Integer> lastNode = doubleLinkedList.getLast(); map.remove(lastNode.key); doubleLinkedList.removeNode(lastNode); }
Node<Integer, Integer> newNode = new Node<>(key, value); map.put(key,newNode); doubleLinkedList.addHead(newNode);
} }
public static void main(String[] args) {
LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);
lruCacheDemo.put(1,1); lruCacheDemo.put(2,2); lruCacheDemo.put(3,3); System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(4,1); System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(3,1); System.out.println(lruCacheDemo.map.keySet()); lruCacheDemo.put(3,1); System.out.println(lruCacheDemo.map.keySet()); lruCacheDemo.put(3,1); System.out.println(lruCacheDemo.map.keySet()); lruCacheDemo.put(5,1); System.out.println(lruCacheDemo.map.keySet());
} }
|