关键在于 代理模式 AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
(1)同步代码块:
在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
(2)同步方法:
在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的锁对象是 this
静态同步方法: 在方法声明上加上static synchronized
静态同步方法中的锁对象是 类名.class
(3)同步锁
Lock接口提供了与synchronized关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
补充:
异步锁:当多个jvm或者服务器 操作同一个变量时 会出现线程安全问题 需要异步锁进行处理:
(1)数据库的乐观锁 悲观锁 不推荐 容易造成锁表 死锁
(2)Redis分布式锁 设置一个flag标识 当一个服务拿到锁之后立即将标识设置为false 用完后释放锁 并且将标识设置为true
(3)使用dubbo zookeeper (共享锁,排它锁),这里就根据自己的情况,共享锁还是会出现阻塞的情况,排它锁就是会生成很多临时的节点,谁先获取最小的序号标识谁就先获取到锁。
SpringBoot启动的时候加载启动类,@EnableAutoConfiguration注解开启了自动配置功能 @EnableAutoConfiguration的作用是利用AutoConfigurationImportSelector给容器中导入一些组件查看selectImports()方法的内容该方法中的 getCandidateConfigurations()方法获取候选的配置 内部的loadFactoryNames()方法扫描所有jar包类路径下 META‐INF/spring.factories把扫描到的这些文件的内容包装成properties对象 从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中(整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中)
每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;每一个自动配置类进行自动配置功能
以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理:
根据@Conditional指定的条件判断,决定这个配置类是否生效 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;所有在配置文件中能配置的属性都是在xxxxProperties类中封装者;配置文件能配置什么就可以参照某个功能对应的这个属性类
类加载:(类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构)
类加载器有哪些:(类加载器采用双亲委派模型)
类加载器的三大特性:委托性、可见性、单一性
1.启动类加载器:这个类加载器负责放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库。用户无法直接使用。不是ClassLoader的子类 其他两个是
2.扩展类加载器:这个类加载器由sun.misc.LauncherKaTeX parse error: Undefined control sequence: \lib at position 32: …。它负责<JAVA_HOME>\̲l̲i̲b̲\ext目录中的,或者被jav…AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。
4.自定义加载器:用户自己定义的类加载器。
1:类的加载过程:
当使用Java命令运行java程序时 此时jvm(虚拟机)启动 并去方法区下寻找java命令后面跟的类是否存在 若不存在 则将这个类加载到方法区下
当类加载到方法区后 会分为两部分执行:先加载非静态内容到非静态方法区域 再加载静态内容到静态方法区域内 当非静态内容加载完后 就会加载所有的静态内容到方法区下的静态区域内
当所有静态内容加载完后 对所有的静态成员变量进行默认初始化 默认初始化完成后 对所有静态成员变量进行显示初始化
完成后 jvm会自动执行静态代码块 (静态代码块在栈中执行)(当有多个静态代码块时 执行顺序为你书写代码的先后顺序)所有代码块执行完毕 也就证明类加载完成
2.对象的创建过程:
3.当使用java命令运行java程序时 此时jvm启动 并去方法区下寻找有没有所创建对象的类存在 有则创建对象 没有就将这个类加载到方法区
在创建类的对象时 首先会去堆内存中开辟一块空间 开辟后分配空间(指定地址) 空间分配完后 会加载该对象中所有的非静态成员变量进行默认初始化 默认初始化后 调用相应构造方法到栈内存中 在栈中执行构造函数时,先执行隐式,再执行构造方法中书写的代码构造方法中的隐式:
第一步:执行super()语句 调用父类的没有参数的构造方法
第二步:对所有的非静态成员变量进行显式初始化(在定义成员变量时后面有赋值)
第三步:显式初始化完成之后,执行构造代码块
Ps.第二步第三步按照书写顺序执行
最后执行构造方法中书写的代码
当整个构造方法全部执行完,此对象创建完成,并把堆内存中分配的空间地址赋给对象名(此时对象名就指向了该空间)
Jvm是一种用于计算机设备的规范,他是一个虚构出来的计算机 Java语言的一个非常重要的特点就是与平台的无关性,而使用java虚拟机是实现这一特点的关键 一般的语言 在不同平台上进行运行 需要编译成不同的目标代码 但是Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行 只需生成在java虚拟机上运行的目标代码,就可以在多重平台上不加修改的运行 这就是“一出编译 多出运行”的原因
在执行Thread.start()方法后,线程是不是马上运行:
当执行start()方法后 线程状态编程runnable(运行)状态 但是线程这个时候并不会马上运行 而是调用native方法 由他在操作系统中新建一个线程 但是操作系统上新创建的线程也是不会立即执行的 他会线程需要被cpu调度,分配了时间片之后才会真正的运行。因此jvm中的RUNNABLE状态其实对应了两个状态,ready和runnable。创建的新线程是ready状态,被cpu调度后成为runnale状态,这时候才是真正的运行状态。
1.8版本用元数据区取代了1.7版本及以前的永久代。元数据区和永久代本质上都是方法区的实现。
不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存(也就是说jvm可以使用外边的内存)。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小
-XX:MetaspaceSize=N:设置metaspace区域的最大值 如果这个参数没有设置,那么就是通过mxbean拿到的最大值是-1,表示无穷大
-XX:MaxMetaspaceSize=N:表示metaspace首次使用不够而触发FGC的阈值,只对触发起作用,原因是:垃圾搜集器内部是根据变量_capacity_until_GC来判断metaspace区域是否达到阈值的
(1)引用计数法 Reference Counting
给对象添加一个引用计数器,每过一个引用计数器值就+1,少一个引用就-1。当它的引用变为0时,该对象就不能再被使用。它的实现简单,但是不能解决互相循环引用的问题。
(2)根搜索算法 GC Roots Tracing
以一系列叫“GC Roots”的对象为起点开始向下搜索,走过的路径称为引用链(Reference Chain),当一个对象没有和任何引用链相连时,证明此对象是不可用的,用图论的说法是不可达的。那么它就会被判定为是可回收的对象。
JAVA里可作为GC Roots的对象
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(即Native方法)的引用的对象
(3)标记-清除算法 Mark-Sweep
这是一个非常基本的GC算法,它是现代GC算法的思想基础,分为标记和清除两个阶段:先把所有所有需要回收的对象标记出来,然后把没有被标记的对象统一清除掉。但是它有两个问题,一是效率问题,两个过程的效率都不高。二是空间问题,清除之后会产生大量不连续的内存。
(4)复制算法 Copying
复制算法是将原有的内存空间分成两块,每次只使用其中的一块。在GC时,将正在使用的内存块中的存活对象复制到未使用的那一块中,然后清除正在使用的内存块中的所有对象,并交换两块内存的角色,完成一次垃圾回收。它比标记-清除算法要高效,但不适用于存活对象较多的内存,因为复制的时候会有较多的时间消耗。它的致命缺点是会有一半的内存浪费。
(5)标记整理算法 Mark-Compact
标记整理算法适用于存活对象较多的场合,它的标记阶段和标记-清除算法中的一样。整理阶段是将所有存活的对象压缩到内存的一端,之后清理边界外所有的空间。它的效率也不高。
两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程 都陷入了无限的等待中。如何预防死锁:
处理死锁的思路如下:
预防死锁:破坏四个必要条件中的一个或多个来预防死锁。
避免死锁:在资源动态分配的过程中,用某种方式防止系统进入不安全的状态。
检测死锁:运行时产生死锁,及时发现思索,将程序解脱出来。
解除死锁:发生死锁后,撤销进程,回收资源,分配给正在阻塞状态的进程。
预防死锁的办法:
破坏请求和保持条件:
1、一次性的申请所有资源。之后不在申请资源,如果不满足资源条件则得不到资源分配。
2、只获得初期资源运行,之后将运行完的资源释放,请求新的资源。
破坏不可抢占条件:当一个进程获得某种不可抢占资源,提出新的资源申请,若不能满足,则释放所有资源,以后需要,再次重新申请。
破坏循环等待条件:对资源进行排号,按照序号递增的顺序请求资源。若进程获得序号高的资源想要获取序号低的资源,就需要先释放序号高的资源。
扩展资料
形成死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源只给一个线程使用 而其他线程阻塞 用完之后 才会把资源让给其他线程 所以传统关系型数据库用这种锁比较多 操作前先上锁)
实现:使用了select…for update的方式将数据锁住 通过开启排他锁的方式实现了悲观锁
如:
select status from t_goods where id=1 for update;
此时id为1的那条数据就被我们锁住了 其他事务必须等这次事务执行完毕才能执行 保证了数据不会被其他事务修改
如:
Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
应用场景:多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适
问题:
1.加锁的机制造成数据库效率变低 并增加死锁的几率
2.在只读事务中 不会产生冲突 没必要用锁 只会增加系统负载压力
3.降低了并行性,一个事务如果锁定了某行数 据,其他事务就必须等待该事务处理完才可以处理那行数
乐观锁:
总是假设最好的情况 每次别人拿数据认为别人都不会进行修改 不上锁 但是在更新数据前会判断别人有没有进行更新(在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号进行实现)乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,都是提供的乐观锁
应用场景:适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量
问题:两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就可能会遇到不可预 期的结果
总结
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机智的其实都是提供的乐观锁。 相反,如果经常发生冲突,上层应用会不断进行 retry,这样反而降低了性能,所以这种情况下用悲观锁比较合适
无论集成谁(excute(runable接口中提交方法)) 都有一个问题 就是无法立刻获取任务执行结果 可通过excutor线程池提供一些callable、future等方法进行提交任务实现:
线程池优点:
线程池关闭相关方法:
Serializable接口:
一般情况下定义:
Serializable接口就是:
一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化
序列化是将对象状态转换为可保持或传输的流格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据
反序列化同理就是将可传输的流格式恢复为对象的过程
这两个过程结合起来,可以轻松地存储和传输数据。
把对象转换为字节序列的过程称为对象的序列化
把字节序列恢复为对象的过程称为对象的反序列化
为什么要定义serialversionUID变量
必看视频!获取2024年最新Java开发全套学习资料 备注Java
:
不定义运行时也会自行创建这个变量,但是强烈建议用户自定义一个serialVersionUID,因为默认的serialVersinUID对于class的细节非常敏感,反序列化时可能会导致InvalidClassException这个异常。用来辅助对象的序列化和反序列化的
详细过程:当序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误InvalidClassException
***声明式事物(即以配置或注解的方式对事物进行声明):
spring给了一个约定(AOP开发也给了我们一个约定),如果使用的是声明式事务,那么当你的业务方法不发生异常(或者发生异常,但该异常也被配置信息允许提交事务)时,Spring就会让事务管理器提交事务,而发生异常(并且该异常不被你的配置信息所允许提交事务)时,则让事务管理器回滚事务
用一个简单例子说明:银行转帐业务,账户A要将自己账户上的1000元转到B账户下面,A账户余额首先要减去1000元,然后B账户要增加1000元。假如在中间网络出现了问题,A账户减去1000元已经结束,B因为网络中断而操作失败,那么整个业务失败,必须做出控制,要求A账户转帐业务撤销。这才能保证业务的正确性,完成这个操走就需要事务,将A账户资金减少和B账户资金增加放到同一个事务里,要么全部执行成功,要么全部撤销,这样就保证了数据的安全性。
事务具有四个特性,也是面试常考的四个特性ACID:
A(原子性Atomicity):原子性指的是事务是一个不可分割的,要么都执行要么都不执行。
C(一致性Consistency):事务必须使得数据库从一个一致性状态,到另外一个一致性状态。
I(隔离性Isolation):指的是一个事务的执行,不能被其他的事务所干扰。
D(持久性Durability):持久性指的是一个事务一旦提交了之后,对数据库的改变就是永久的。
事务其实可以划分为两大类:隐式的事务和显示的事务
隐式的事务很简单,比如我们的insert、delete、update、select这些语句都是隐式的事务。
显示的事务指的是带有很明显的开始和结束的标记,下面就来创建一个显示的事务。
步骤一:禁用步骤提交功能
set autocommit = 0;
步骤二:开启一个事务
start transaction;
步骤三:sql语句
update table user set money=500 where name = “张三”;
update table user set money=1500 where name = “李四”;
步骤四:结束事务
commit(提交)或者是rollback(回滚)。如果确定我们的语句没有问题,那么我们就可以commit,如果认为我们的语句有问题,那就rollback。
在这里新建了一个表,然后插入了两条数据。下面我们使用事务,来更新一下:
在这里我们使用的是commit进行提交。当然如果突然发现我们之前的操作有错误,那就可以使用rollback。
我们接下来就看看这四个隔离级别的具体情况
读未提交(Read Uncommitted)
读未提交,顾名思义,就是可以读到未提交的内容。
因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。
如无特殊情况,基本是不会使用这种隔离级别的。
读提交(Read Committed)
读提交,顾名思义,就是只能读到已经提交了的内容。
这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。这种隔离级别能够有效的避免脏读,但除非在查询中显示的加锁,如:
select * from T where ID=2 lock in share mode;
select * from T where ID=2 for update;
不然,普通的查询是不会加锁的。
那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?
这就要说道另一个机制“快照(snapshot)”,而这种既能保证一致性又不加锁的读也被称为“快照读(Snapshot Read)”
假设没有“快照读”,那么当一个更新的事务没有提交时,另一个对更新数据进行查询的事务会因为无法查询而被阻塞,这种情况下,并发能力就相当的差。
而“快照读”就可以完成高并发的查询,不过,“读提交”只能避免“脏读”,并不能避免“不可重复读”和“幻读”。
可重复读(Repeated Read)
可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。
在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。
这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。
这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。
上面的事务在单个情况下一般不会出现什么问题,但是如果同时运行多个,就会出现问题了。我们知道并发操作总是会出现各种各样的问题,对于事务来说就会出现下面三个典型的问题:
(1)脏读
有俩事务T1,T2。如果T1读了一条数据,这条数据是T2更新的但是还没提交,突然T2觉得不合适进行事务回滚了,也就是不提交了。此时T1读的数据就是无效的数据。
(2)不可重复读
有俩事务T1,T2。如果T1读了一条数据,之后T2更新了这条数据,T1再次读取就发现值变了。
总结
阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了
1、JAVA面试核心知识整理(PDF):包含JVM,JAVA集合,JAVA多线程并发,JAVA基础,Spring原理,微服务,Netty与RPC,网络,日志,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
2、Redis学习笔记及学习思维脑图
3、数据面试必备20题+数据库性能优化的21个最佳实践
总结
阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了
[外链图片转存中…(img-on8p9Y4U-1716456720869)]
1、JAVA面试核心知识整理(PDF):包含JVM,JAVA集合,JAVA多线程并发,JAVA基础,Spring原理,微服务,Netty与RPC,网络,日志,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
[外链图片转存中…(img-lPjgzcwW-1716456720869)]
2、Redis学习笔记及学习思维脑图
[外链图片转存中…(img-J25Aj0VV-1716456720870)]
3、数据面试必备20题+数据库性能优化的21个最佳实践
[外链图片转存中…(img-Xuds2iRh-1716456720870)]