Java基础

Java字符串常量池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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());


}
}

运行结果:

1
2
3
4
5
6
7
8
58tongcheng
58tongcheng
true
------------
java
java
false

intern方法

image-20210301110628175

有一个初始化的java字符串(JDK出娘胎自带的), 在加载sun.misc.Version这个类的时候进入常量池

在这里插入图片描述

image-20210301111352082

JUC

image-20210302150605425

可重入锁

概述

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。

Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁

Synchronzied: 隐式锁 JVM控制 【自动挡】

ReentrantLock: 显示锁Lock ,手动来写 【手动挡】

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ReEnterLockDemo1 {
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();
}
}

结果:

image-20210302152028995

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ReEnterLockDemo1 {

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 ReEnterLockDemo1().m1();
}
}

结果:

image-20210302152447984

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
public class ReEnterLockDemo1 {

static Lock lock = new ReentrantLock();

public static void main(String[] args) {
new Thread(() -> {
lock.lock();
lock.lock();
try {
System.out.println("=====外层");
lock.lock();
try {
System.out.println("=====内层");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
lock.unlock();
}
}, "t1").start();
}
}

结果:

image-20210302153124791

LockSupport

是什么

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park()和unpark()的作用分别是阻塞线程和解除阻塞线程

三种让线程等待唤醒的方法

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
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();
}
}

必须结合synchronized,notify()放于wait 之前无法执行,无法唤醒,程序一直等待下去。

必须成对,先wait再notify

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
public class LockSupportDemo {
static Object objectLock = new Object();
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LockSupportDemo {


public static void main(String[] args) throws InterruptedException {

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.sleep(3000);

Thread B = new Thread(() -> {
LockSupport.unpark(A);
System.out.println(Thread.currentThread().getName() + "\t" + "---------发出通知");
}, "B");
B.start();
}
}

单纯的唤醒和阻塞线程,不需要再锁里面

原理

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却需要消费两个凭证,证不够,不能放行。

AbstractQueuedSynchronizer之AQS

在这里插入图片描述

是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态

Spring

AOP

AOP常用注解

image-20210308142702580

问题

image-20210308142811250

从spring boot1到spring boot2 ,spring从4升级到了5,AOP发生了哪些变化

Spring4+springboot1

AOP顺序:

环绕前

前置

业务逻辑

环绕后

后置

返回通知

image-20210308143813033

异常:

环绕前

前置

后置

异常通知

image-20210308143841793

Spring5+Springboot2

正常:

环绕前

前置

业务逻辑

返回通知

后置通知

环绕后

image-20210308145159449

异常:

环绕前

前置

异常

后置

image-20210308145310147

总结

image-20210308145728621

循环依赖

image-20210309150633892

什么是循环依赖

多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A

image-20210309150827272

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

image-20210309150852874

也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题

构造器方式注入依赖

构造器注入没有办法解决循环依赖

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

/**
* 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
*
* 测试后发现,构造器循环依赖是无法解决的
*/
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
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
13

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
public class ClientSet {
public static void main(String[] args) {

//创建serviceA
ServiceA serviceA = new ServiceA();

//创建serviceB
ServiceB serviceB = new ServiceB();

//将serviceA注入到serviceB中
serviceB.setServiceA(serviceA);

//将serviceB注入到serviceA中
serviceA.setServiceB(serviceB);

}

可以解决但是不推荐

引入容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

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
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);
}
}

默认的单例(singleton)的场景是支持循环依赖的,不报错

原型(Prototype)的场景是不支持循环依赖的,报错

Spring通过三级缓存解决循环依赖

DefaultSingletonBeanRegistry

image-20210309152820179

  1. 第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象
  2. 第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)
  3. 第三级缓存: Map> singletonFactories,存放可以生成Bean的工厂

image-20210309152915984

三大缓存四大方法

image-20210309153511896

三级缓存+四大方法

  1. getSingleton:希望从容器里面获得单例的bean,没有的话
  2. doCreateBean: 没有就创建bean
  3. populateBean: 创建完了以后,要填充属性
  4. addSingleton: 填充完了以后,再添加到容器进行使用

第一层singletonObjects存放的是已经初始化好了的Bean,

第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,

第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

迁移

  1. A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B

  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
    然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A

  3. B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
    然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

总结

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,走剩下的流程。

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从二级缓存移动到一级缓存中

Redis

image-20210310154616644

String

image-20210310154828183

image-20210310155006960

应用:喜欢文章,喜欢商品:INCR 每点一下 加一

Hash

image-20210310155345393

应用场景:购物车

image-20210310155459087

list

image-20210310155922174

应用场景:

微信文章订阅号

image-20210310155948148

set

无序无重复

image-20210310160315118

应用场景:

微信抽奖:

1 用户ID,立即参与按钮 sadd key 用户ID

2 显示已经有多少人参与了,上图23208人参加 SCARD key

3 抽奖(从set中任意选取N个中奖人) SRANDMEMBER key 2 随机抽奖2个人,元素不删除
SPOP key3 随机抽奖3个人,元素会删除

微信点赞:image-20210310160935895

微信社交关系:并集

可能认识的人:交集

zset

image-20210310161445187

应用场景:

根据商品销售对商品进行排序显示

image-20210310161630501

热搜:

image-20210310161818653