面试题和问题总结-问题总结

目录

1、线程池

线程池几种状态(状态的前3位是状态计算,后29位为线程数量计算)

private static final int RUNNING    = -1 << 29; 二进制表示1110.0000290
private static final int SHUTDOWN   =  0 << 29; 二进制表示0000.0000320
private static final int STOP       =  1 << 29; 二进制表示0010.0000290
private static final int TIDYING    =  2 << 29; 二进制表示0100.0000290
private static final int TERMINATED =  3 << 29; 二进制表示0110.0000290

shutdown

//调用shutdown和shutdownNow后,不能再提交任务了。如果是shutdown,可以提交一个空任务的工作线程用于帮助处理队列中的任务
//当调用shutdown时,正在阻塞的工作线程(一般为核心线程或者设置了等待时间的非核心线程)会被中断(interruptIdleWorkers),被中断的线程在getTask方法里重新开始循环,
//1、当队列为空时,getTask返回null,被中断的线程就结束了。
//2、队列不为空,继续去队列拿任务执行,拿不到任务则返回null,被中断的线程就结束了。 
public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            // 在runWorker方法里,会获取每个任务(Worker)的锁
            // 而interruptIdleWorkers也是去获取每个任务(Worker)的锁,获取成功了才会调用                       // 这个线程的interrupt方法, 如果正在执行的线程             
            // 是获取不到锁的,也就不会调用这个线程的interrupt方法
            // 也就是说任务只是addWork了,但是还没有开始运行,将这些还没开始运行的任务中断,                       // 已经提交的任务则等待其完成
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

shutdownNow

  public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            // 不用获取worker的锁,直接调用线程的interrupt方法中断所有线程,
            // 并将队列清空
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
    
            // 1、当调用shutdown时,如果队列不为空,会继续执行队列中的任务
            // 2、当调用shutdownNow时,任何工作线程这里直接返回null,最终线程池退出
            // 当调用shutdown时,会将运行状态设置为shutdown,
            // 当调用shutdownNow时,会将运行状态设置为stop,
            // 条件成立直接返回null,外部的while循环就可以退出了,那么线程池也就退出了
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // 当队列为空后,非核心线程直接返回null
            // 当超过最大线程数,超过的线程直接返回null,然后被回收
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 核心线程和非核心线程都会去队列拉取任务,
                // 直到拉取到任务后退出当前getTask方法。
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                // 核心线程是不会走到这一步的,前面直接返回了r
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            // 1、从workers中移除当前工作线程
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        // 2、如果状态为RUNNING或者为SHUTDOWN并且队列不为空,不做任何操作
        //   将STOP状态先设置为TIDYING,然后设置为TERMINATED
        tryTerminate();
        // 3、如果状态为RUNNING或者SHUTDOWN,那么如果核心线程数不够了再添一个线程,维持核心线程数不变
        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

2、并发

CountDownLatch原理

// 初始化AQS的state值等于3

CountDownLatch ctl = new CountDownLatch(3);

主线程调用await阻塞,调用一次countDown()则state值减一,直到最后一个线程将state的值减为0的时候,唤醒阻塞的主线程。

线程同步方式

synchronized,juc工具类(CountdownLatch,CyclicBarrier,Semaphore)

3、缓存

CyclicBarrier原理

// 初始化CyclicBarrier成员变量count值,并且使用一个常量parties备份这个值,用于后面重置

CyclicBarrierctl = new CyclicBarrier(3);

每个线程调用await方法,调用时将count的值减1然后判断count的值是否等于0,,如果不等于零则使用condition.await方法阻塞。

直到最后一个线程将count减到0,然后将condition队列所有节点搬到主队列(signalAll方法),最后一个线程释放锁,等待主队列所有节点排队获取锁执行。

谷歌 guava cache

数据库建立一个索引就会维护一个索引树,所以不能创建太多索引

查找数据过程:根据索引找到数据页,然后从数据页中查找到记录

执行计划中的 select_type 可以为simple,union,subquery

4、Spring相关

spring注册主类beanDefinition

SpringApplication.prepareContext
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		.................................
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
}
protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug(
					"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(
				getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}

springboot根据ComponentScan扫描目录下的bean

在refresh方法的invokeBeanFactoryPostProcessors阶段调用ConfigurationClassPostProcessor后置处理器的方法,内部调用ConfigurationClassParser的parse方法,内部又调用ClassPathBeanDefinitionScanner.scan方法

ConfigurationClassPostProcessor的beanDefinition什么时候注册的

AnnotationConfigServletWebServerApplicationContext构造函数初始化时,new了AnnotatedBeanDefinitionReader,在AnnotatedBeanDefinitionReader的默认构造里注册了

ConfigurationClassPostProcessor的beanDefinition,AutowiredAnnotationBeanPostProcessor也是这时候注册的

@Import注解内的selector

aplicationContextAware什么时候执行的

ApplicationContextAwareProcessor后置处理器进行处理

在AbstractApplicationContest的refresh方法,创建完beanFactory后,prepareBeanFactory方法内new了一个ApplicationContextAwareProcessor后置处理器。

AbstractRefreshableApplicationContext和GenericApplicationContext两个new了DefaultListableBeanFactory

spring为什么使用三级缓存?

二级缓存用来存放代理后的对象,防止多次执行代理造成产生多个代理对象。

因为二级缓存无法解决相互依赖的aop对象。

a–>b,b–>a,c,c–>a

b去二级缓存获取代理后的a,c也去二级缓存获取代理后的a,a被代理了两次,为两个不同的代理对象,就不是单例了。

springbean的生命周期

1、实例化

2、初始化

3、如果bean实现了各种aware接口,那么调用这些接口的方法。

4、后置处理器的before处理bean

5、如果实现了InitializingBean接口,那么调用接口方法

6、如果有自定义init-method方法,那么执行

7、后置处理器的after处理bean

5、java语言特征

注解保留策略

// 注解会被编译到class文件,并且运行时jvm能够访问到注解
@Retention(RetentionPolicy.RUNTIME)
// 注解编译后会被抛弃
@Retention(RetentionPolicy.SOURCE)
// 注解会被编译到class文件,但运行时访问不到注解
@Retention(RetentionPolicy.CLASS)

为什么Java中不支持多重继承?

多继承设计有缺陷,会导致菱形继承问题。

c++处理多继承是使用虚继承,即继承类时加virtual关键字。

虽然c++支持多继承,但是并不建议使用多继承

6、网络相关

浏览器输入url后前后端发生了什么?

1、查询域名对应ip

1.1、首先查找浏览器缓存,如果不存在然后查找操作系统缓存,如果还不存在则去查找路由器缓存,如果还找不到那么去isp通信运营商的缓存

2、浏览器获取到目标ip后三次握手建立连接,封装请求数据

3、arp请求

地址解析协议,广播得到下一跳的mac地址,将目标ip和下一跳mac地址再加上请求数据传给下一跳

网络层和链路层区别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajC8Wgch-1625063306902)(总结1.assets/image-20210509180059586.png)]

网络层:访问目标百度的ip,220.181.38.149,将220.181.38.149与路由表所有项的掩码进行与运算,最终和第三个0.0.0.0的与运算结果为0.0.0.0,匹配上了第三个的destination,则将数据传递给第三个的网关地址,192.168.150.2(下一跳的地址,路由器)

链路层:封装网关(192.168.150.2)的mac地址(arp广播获取)和目标的ip地址和端口(百度220.181.38.149)

网关:连接基于不同通信协议的网络的设备

网络层和链路层区别:

网络层使用路由表找到要发送的目标网关。

链路层使用arp获取发送目标的mac地址进行目标寻找

怎么实现幂等

乐观锁、前端按钮置灰、请求跳转

防重放攻击

窃取到的数据原封不动再发送给接受方,常用于身份认证过程.

怎么预防?

1、随机数,如果两次随机数一致则为非法请求 。

2、时间戳,如果两次时间戳一致则为非法请求

3、自增序列,两次请求序列一致或者不是自增步长,则为非法请求

7、jvm相关

String长度限制

编译期长度为65534,超过会报错。运行时为整形最大值长度。

编译期String是要存储在常量池中的,而常量池中数据是用无符号数和表存储的,String对应的表结构用utf-8常量表来描述,而utf-8对应的常量表长度为65535,其中一个长度用于表示结束标志,所以编译器最大长度为65534,

CONSTANT_String_info{

​ u1 tag;

​ u2 string_index; //结构使用如下utf-8常量表表示

}

CONSTANT_Utf8_info{

​ u1 tag;

​ u2 length;

​ u1 bytes[length];

}

java类初始化过程(类变量初始化)

加载1、获取class文件二进制字节流(Class.forName())。2、将二进制流代表的静态存储结构转换为方法区运行时数据结构。3、在方法区生成java.lang.Class对象
验证验证class文件是否符合Jvm规范
准备类变量 赋值零值,final修饰的在准备阶段会赋予指定的值,public static int value = 123,准备阶段value值为0,public static final int value = 123,准备阶段value值为123
解析将常量池内的符号引用替换为直接引用
初始化类变量 赋初始值,只能由一个线程执行静态变量的< clinit > 指令,且只能执行一次

对象创建过程(成员变量初始化)

遇到new指令时,

1、先去检查类是否已被加载并被解析和初始化,如果没有,那么先执行加载过程

2、为对象分配堆内存(空闲列表、指针碰撞(分配安全cas加失败重试)、TLAB)

3、将分配的空间都初始化为零值,

4、将对象头设置一些信息(hashcode(在真正调用Object::hashCode时才计算))

5、调用< init>方法,初始化属性值

什么情况下会触发类的初始化

1、new对象

2、使用java.lang.reflect包的方法对类型进行反射调用的时候

3、访问静态变量

4、访问静态方法

5、初始化子类会导致父类先初始化

java内存模型

屏蔽不同硬件和操作系统内存访问差异。定义程序中各种变量的访问规则。

1、变量都储存在主内存中

2、工作内存和主内存交互java内存模型规定了8种操作

3、volatile关键字

4、long和double非原子性协议

5、原子性、可见性、有序性

6、先行发生原则

Java内存模型定义了程序中各个变量的访问规则,规定变量只能从主内存中产生,每个线程有自己的工作内存,主内存和工作内存之间的交互是用8种原子性操作来交互的。同时java内存模型还处理并发中的原子性,可见性,有序性。其中原子性可以使用synchronized来保证,而可见性和有序性使用volatile关键字保证。有序性还可以用先行发生原则保证

as-if-serial,within-thread,happen-before

java虚拟机栈在调用方法时,创建一个栈帧,栈帧包括操作数栈、局部变量表,

对于非静态方法,局部变量表索引0的位置存的是this。

计算的中间结果保存在操作数栈。

8、操作系统相关

进程通信方式

管道、rpc、消息队列、共享内存、socket、信号量

孤儿进程

父进程退出,子进程未退出,这些子进程称为孤儿进程。

僵尸进程

父进程未取得子进程的退出状态,这些退出状 态还在进程表中存在并占用进程表,这些已退出的占用资源子进程成为僵尸进程。

僵尸进程解决方法

kill掉父进程,僵尸进程就会变成孤儿进程并被init进程接管,init进程会释放这些占用的进程表资源的僵尸进程。

9、redis

redis内存淘汰策略

a) 针对设置了过期时间的key做处理:
	1、volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
	2、volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
	3、volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。
	4、volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。
b) 针对所有的key做处理:
	5、allkeys-random:从所有键值对中随机选择并删除数据。
	6、allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。
	7、allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
c) 不处理:
    8、noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error)OOM command not allowed when used memory",此时Redis只响应读操作。

数据删除策略

1、被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
2、主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
3、当前已用内存超过maxmemory限定时,触发主动清理策略
4、LRU 算法(Least Recently Used,最近最少使用):淘汰很久没被访问过的数据,以最近一次访问时间作为参考。
5、LFU 算法(Least Frequently Used,最不经常使用):淘汰最近一段时间被访问次数最少的数据,以次数作为参考

持久化策略

第一种是快照(RDB),第二种是 AOF 日志。快照是一次全量备份,AOF 日志是连续的增量备份。RDB记录的是数据,AOF记录的是指令。应选择rdb和aof混合模式,这样aof只会增量记录rdb之后的日志,重启时候,先加载rdb数据,再加载aof数据

redis哨兵

客户端连接的时候连接哨兵的ip端口。哨兵中每个redis都是全量的数据。

redis集群

和哨兵的区别是数据分片存储,能存更多的数据。

redis主从同步

1、slave启动后会发送sync命令给master,master收到命令后在后台保存快照(RDB持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和缓存的命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。

2、master每执行一个写命令都会同步发送给slave。

redlock原理

redis集群:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0EEntGpU-1625063306904)(C:\Users\root\Desktop\面试总结\总结.assets\image-20210610113854869.png)]

1、多节点redis实现的分布式锁算法(RedLock)

主redis同步数据到从redis时,还没同步过去,master挂了,导致从redis里没有锁信息,从选举成为master以后,其他客户端可以继续从master中获取锁。

如果从redis选举成为主以后,等锁过期后再去获取锁,或者等锁过期后再去选举主

2、为什么要半数redis(master)实例获取锁?

假如共5个实例,第一个客户端在两个实例上获取了锁,第二个客户端也可也在剩下的3个实例中获取锁。导致锁被同时持有。

假如共5个实例,第一个客户端已经获取了3个实例的锁,这时候挂了一个实例,第二个客户端只能在剩下的4个里面获取两个实例的锁,没有获取半数以上的锁,获取锁失败。

当一个客户端获取了半数以上的锁,其他获取了半数以下的就必须要释放锁。

3、终极解决高可用分布式锁方案

redis持久化选择每秒同步,redis宕机后等待ttl后再重启(延时重启)

4、在过期时间内,获取了半数以上实例的锁才算获取锁成功

开启AOF

appendonly yes

appendfsync everysec

将flushall命令从aof文件中删除再重启redis,数据会恢复

redis脑裂怎么产生的,怎么减少脑裂产生的数据损失

一个master和slave不能连接了,哨兵会认为master已经挂了,再次选举一个master,这时有2个master。客户端还没来得及切换到新master,继续向老的master写数据,等老master回复连接后,变成slave,清空数据再从新master同步数据,数据丢失。

配置

Min-slaves-to-write 1 
Min-slaves-max-lag 10

如果slave10秒没有回复,那么master拒绝客户端写入数据,那么最终会丢失10秒的数据。

10、事务相关

数据库事务

读提交、读未提交、可重复读、序列化

mysql默认隔离级别为可重复读

spring事务传播机制

PROPAGATION_REQUIRES_NEW每次创建一个新的事务并挂起当前事务
PROPAGATION_REQUIRED支持当前事务,如果没有事务会创建一个新的事务
PROPAGATION_SUPPORTS支持当前事务,如果没有事务的话以非事务方式执行
PROPAGATION_NOT_SUPPORTED以非事务方式执行,如果当前存在事务则将当前事务挂起
PROPAGATION_MANDATORY支持当前事务,如果没有事务抛出异常
PROPAGATION_NEVER以非事务方式进行,如果存在事务则抛出异常
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

分布式事务

1、TCC(try,confirm.cancel)分布式事务,所谓分布式事务,回滚方式在cancel方式里实现,你confirm里insert了一条数据,在cancel里就要delete删除这条数据,如果calcel失败了就会一直重试。

2、事务补偿机制

即事务内的多个原子操作,其中一个操作失败了,尝试一定的次数,如果多次执行失败,那么将操作放入消息队列,进行失败重试

11、数据库和缓存

mysql页分裂

数据页之间双向链表连接,如果插入的数据索引不是自增的,那么会将数据页重新维护

如何保证缓存和数据库一致性

延时双删,先删除缓存,再更新数据库,等一段时间,再次删除缓存

1、时间等多长 ?
根据其他线程读取key时间来确定,在其执行读取方法的耗时之后再次删除缓存

2、如果用了mysql读写分离架构怎么办?
一样用延时双删,不过第二次删除时间为 读取方法的耗时 加上 主从同步时间

3、采用这种同步淘汰策略,吞吐量降低怎么办?
异步执行第二次删除缓存

4、删缓存失败了怎么办?
重试机制,将需要删除的key放入消息队列或者数据库,失败了重试删除。
缺点:对业务线代码造成大量的侵入

5、缓存击穿解决
1、使用互斥锁或分布式锁,
2、是设置热点数据永不过期

6、缓存雪崩解决
1、key设置随机失效时间
2、缓存集群

7、缓存穿透
布隆过滤器
缓存空值(当然要设置过期时间)

12、队列

ArrayBlockingQueue、LinkedBlockingQueue

13、锁升级过程

当对象处于偏向锁状态时,如果收到计算hashcode请求,那么就会升级为重量级锁,因为重量级锁对象ObjectMonitor对象里存有原始markword。

当对象计算过hashcode后,无法进入偏向锁状态了。

13.1、偏向升级轻量锁

当存在锁竞争时,准备撤销偏向锁,获取了偏向锁的线程如果还未执行完代码,那么升级为轻量级锁,获取偏向锁的线程持有轻量级锁,如果执行完代码了,那么将markword设置为无锁不可偏向状态。

13.2、轻量升级重量锁

当存在锁竞争时,线程2看到markword里的指针不是指向自己的栈帧,那么准备膨胀为重量级锁,线程2使用cas操作将markword设置为正在膨胀状态,然后为ObjectMonitor对象设置一些属性,其中owner属性设置为获取轻量级锁的线程1,而不是线程2,即已经获取了锁正在执行代码的线程。cas失败的等待膨胀完成(根据markword是否有指向ObjectMonitor对象的指针判断)。最终获取了轻量级锁的线程1执行释放重量级锁的过程。

13.1、重量级锁获取过程:

重量级锁对象ObjectMonitor对象里有owner、recursion、entryList、waitSet等属性,

调用monitor:enter方法时,和AQS流程大致一致,首先cas设置owner为当前线程,设置成功则加锁成功,如果是第一次获取,那么recursion加一,如果cas失败,当owner等于当前线程时,recursion加一,这就是重入。

14、springmvc执行过程

15、为什么区分度不高的字段不加索引(性别)

如果一共100条数据,一共80个男的,select * from table where sex = ‘1’, 需要回表80次,还不如直接全表扫

16、字段空值有什么影响

17、索引下推

组合索引中,第一个条件满足后继续匹配第二个条件,减少回表次数

set optimizer_switch='index_condition_pushdown=off';

18、ThreadLocal

ThreadLocal,如果外部没有对其强引用,那么gc的时候ThradLocal会被回收,那么ThreadLocalMap就会出现key为null的Entry,那么entry的value不会被回收,如果线程迟迟不结束,造成内存泄漏。

19、@EnableCaching

new CacheOperationContext时候获得Cache实现类

该注解@Import了CachingConfigurationSelector类,分别将AutoProxyRegistrar和ProxyCachingConfiguration注册到spring容器,其中ProxyCachingConfiguration配置类会将CacheInterceptor注册到spring,缓存的具体实现就在CacheInterceptor内部

20、SpringAop

1、AnnotationAwareAspectJAutoProxyCreator后置处理器的创建

spring.factories内配置的自动配置类AopAutoConfiguration,类上ConditionalOnClass注解内容EnableAspectJAutoProxy注解,EnableAspectJAutoProxy注解上方@Import导入了AspectJAutoProxyRegistrar类,为ImportBeanDefinitionRegistrar接口的实现类。在CongifurationClassParser解析@Import注解时调用ImportBeanDefinitionRegistrar接口的方法,AnnotationAwareAspectJAutoProxyCreator的beanDefinition会被注册到spring

2、在AnnotationAwareAspectJAutoProxyCreator后置处理器方法中创建代理(jdk或cglib)
1、创建CglibAopProxy或JdkDynamicAopProxy对象

AnnotationAwareAspectJAutoProxyCreator间接实现了SmartInstantiationAwareBeanPostProcessor接口,所以在bean创建完成后会调用其后置处理方法,在后置处理器方法中使用ProxyFactory(extends ProxyCreatorSupport).getProxy()来创建代理,在ProxyFactory的父类ProxyCreatorSupport默认构造函数里new DefaultAopProxyFactory();调用DefaultAopProxyFactory的createAopProxy方法,返回CglibAopProxy或JdkDynamicAopProxy对象,

AbstractAutoProxyCreator创建ProxyFactory时候,为ProxyFactory设置Advisor实现类(AspectJAroundAdvice)

springMVC方式是一样是将AnnotationAwareAspectJAutoProxyCreator的beanDefinition注册到spring

2、获取代理对象

CglibAopProxy在getProxy创建代理类的时候,设置了DynamicAdvisedInterceptor回调,调用回调的intercept方法时,new CglibMethodInvocation,调用其proceed方法。

ReflectiveMethodInvocation.proceed---->AspectJAroundAdvice.invoke,ProceedingJoinPoint作为@Around方法的参数,其实是将ReflectiveMethodInvocation包装到了ProceedingJoinPoint实现类里面,调用ProceedingJoinPoint.proceed实际上是调用ReflectiveMethodInvocation.proceed,当创建的代理类的拦截器都执行完以后就执行默认的@Around修饰的方法。

自己实现一个aop注解
1、advisor
public class MyAopAspectjAdvisor implements PointcutAdvisor {

    private Pointcut pointcut = new MyAopAspectJPointcut();

    private Advice advice = new MyAopAspectjAdvice();

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    public Advice getAdvice() {
        return this.advice;
    }

    @Override
    public boolean isPerInstance() {
        return true;
    }

    public void setPointcut(Pointcut pointcut) {
        this.pointcut = pointcut;
    }

    public void setAdvice(Advice advice) {
        this.advice = advice;
    }
}

2、advice
public class MyAopAspectjAdvice implements MethodInterceptor {
    private static final Logger log = LoggerFactory.getLogger(MyAopAspectjAdvice.class);
    public MyAopAspectjAdvice(){
    }
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("-------------------------------------------------------------------自定义增强器");
        return invocation.proceed();
    }
}
3、pointcut
public class MyAopAspectJPointcut implements Pointcut {
    @Override
    public ClassFilter getClassFilter() {
        return ClassFilter.TRUE;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return new MyAopMethodMatcher();
    }
}

4、ClassFilter
5、MethodMatcher
public class MyAopMethodMatcher implements MethodMatcher {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        Proxy annotation = AnnotationUtils.findAnnotation(method, Proxy.class);
        return annotation != null && annotation.isProxy();
    }

    @Override
    public boolean isRuntime() {
        return false;
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass, Object... args) {
        return false;
    }
}

关于DeclareParents注解

DeclareParents和pointcut一样也会为表达式匹配的类生成代理,会为匹配的类增加一个接口实现,

真正调用这个接口的实现是注解里声明的defaultImpl实现类对应的方法。


@Aspect
@Component
public class ProxyTestConfig {
    
    @DeclareParents(value = "com.springboot.service.*", defaultImpl = TestDeclareParentImpl.class)
    private ITestDeclareParent testDeclareParent;

    @Pointcut("execution(* com.springboot.service..*.*(..))")
    public void around() {
    }

    @Around("around()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("around start......");
        return joinPoint.proceed();
    }
}

public interface ITestDeclareParent {
    String hello();
}

// 并不需要放入spring容器
public class TestDeclareParentImpl implements ITestDeclareParent{
    @Override
    public String hello(){
        System.out.println("===============================================================");
        return "zs";
    }
}

@RunWith(SpringRunner.class)
@SpringBootTest(classes={SpringBootStarterApplication.class})
public class SpringbootTest {

    @Autowired
    private IProxyTestService IProxyTestService;

    @Test
    public void testProxy() {
        ((ITestDeclareParent)IProxyTestService).hello();
    }
}


21、springboot的jar为什么可以直接运行?

java -jar xxx.jar

springboot项目使用spring-boot-maven-plugin打包后的结构如下

META-INF

org

WEB-INF

其中META-INF目录为jar包运行入口,目录下的MANIFEST.MF文件描述java -jar 运行的主类,

其中有这两行描述

Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.DebugSpringbootApplication

会先调用JarLauncher的main方法,然后获取MANIFEST.MF文件里的Start-Class,反射实例化并调用其main方法,这正是springboot的启动类

22、MAVEN

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.5.RELEASE</version>
                <!-- 父模块使用spring-boot-maven-plugin打包时,
                子模块如果需要打包为不可执行的jar,使用如下配置,会打包成两个jar,带exec后缀的为可执行jar-->
                <configuration>
                	<classifier>exec</classifier>
                </configuration>
            </plugin>
        </plugins>
    </build>

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
     		<!--不会被打包-->
            <scope>provided</scope>
               <!--表示两个项目之间依赖不传递-->
            <optional>true</optional>
        </dependency>

老年代中数组的访问方式

GC 有环怎么处理

DDD领域模型,是一种开发方式,通常我们改一个功能可能会影响其他功能,DDD要求我们划分界限,使用抽象精简问题,抽象一个边界,比如对接支付接口,不关心是微信还是支付宝支付,我们只对接支付接口

priorityQueue

 private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            // 选出删除位置的左右子节点中的最小值
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            // 如果最后一个节点小于左右节点中的最小值
            // 直接将最后一个节点放在删除节点的位置
            if (key.compareTo((E) c) <= 0)
                break;
            // 否则将左右节点中的最小值放在删除节点的位置
            queue[k] = c;
            // 从左右节点中最小节点的位置继续
            k = child;
        }
        queue[k] = key;
    }

代中数组的访问方式

GC 有环怎么处理

DDD领域模型,是一种开发方式,通常我们改一个功能可能会影响其他功能,DDD要求我们划分界限,使用抽象精简问题,抽象一个边界,比如对接支付接口,不关心是微信还是支付宝支付,我们只对接支付接口

priorityQueue

 private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            // 选出删除位置的左右子节点中的最小值
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            // 如果最后一个节点小于左右节点中的最小值
            // 直接将最后一个节点放在删除节点的位置
            if (key.compareTo((E) c) <= 0)
                break;
            // 否则将左右节点中的最小值放在删除节点的位置
            queue[k] = c;
            // 从左右节点中最小节点的位置继续
            k = child;
        }
        queue[k] = key;
    }

零拷贝

零拷贝技术是基于PageCache

传输大文件使用异步或直接io,即不使用pageCache,小文件使用零拷贝

pageCache

使用了内核的pageCache,即高速缓存区。

pageCache有两个功能:

1、缓存最近被访问的数据,

2、预读数据,比如read方法读32kb,那么pageCache就预读后面的32kb。

零拷贝的过程

pageCache缓存每次访问的文件,当下次再次访问时直接返回,即零拷贝,不再去磁盘上再次拷贝数据。

当然这是针对小文件,大文件不应该使用零拷贝,因为大文件会占用pageCache,导致其他小文件无法使用零拷贝。

大文件应该使用异步io+直接io,不使用pageCache的叫直接io,使用的叫缓存io。

也就是说内核直接将磁盘数据拷贝到用户空间,而不再拷贝到内核缓冲区。

在这里插入图片描述

DMA

DMA,直接内存访问技术,直接存储器,每个io设备都有一个DMA控制器,

直接与内存进行数据交换。

在文件传输过程中,用户空间基本不会对数据进行加工,所以用户缓冲区不必存在。
在这里插入图片描述
传统read,write是4次上下文切换。

用户程序调用read方法后,用户态转为内核态,read方法返回数据则是内核态转为用户态。

write同理。

还有4次数据拷贝,其中2和4为cpu执行拷贝

1、磁盘拷贝到内核,

2、内核拷贝到用户空间,(read)

3、用户空间拷贝到socket缓冲区,(write)

4、socket缓冲区拷贝到网卡。

kafka

ISR(副本同步队列)

因为是异步同步数据,当leader写入数据时,宕机了,那么会造成数据丢失,没有将数据同步到副本,但是有了ISR,当恢复后会将数据同步到副本。

GC安全点和安全区域

gc安全点

用户线程执行到某段代码,这段代码不会改变引用关系

gc安全区域

用户线程在某段代码之间不会改变引用关系。当用户线程执行到安全区域代码时,标识自己已经进入,当准备退出安全区域时,会看gc是否结束,没结束则一直等待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值