目录
Spring事务@Transitional失效的 8 大原因
Spring事务传播属性 在 spring的 TransactionDefinition接口中一共定义了六种事务传播属性:
反射机制
反射机制常用的class
Java.lang.Class
Java.lang.reflect.Constructor
Java.lang.reflect.Field
Java.lang.reflect.Method
Java.lang.reflect.Modifer
//取得class对象
Class userklass = Class.forName(“”);
//获取字段
Field[] array = userklass.getFields();//公有字段
Userklass.getDeclaredFields();// 获得所有字段
User.getField(“”) //获得字段调用
Object obj = userKlass.getConstructor.nextInstance//获取对象
反射优点: 动态访问 对象的字段 方法
缺点: 效率比较低
JMM
JMM模型用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量
JMM 内存交互操作有8种:
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
@Resource 和 @Autowire的区别
@Resource:是jdk自带的annotation,默认按 byName自动注入。@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。如果都找不到,则抛出异常。
@Autowired:属于Spring的注解,默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:
1 2 |
|
HashMap 底层结构
HashMap 由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
Hasmap初始容量 16 负载因子0.75, 自增长容量为原来的2倍,按2的幂次自增。
Concurrencymap 如何实现线程安全
Collections.synchronizedMap
HashTable
使用的是全局锁,把整个hash表都lock
Concurrecymap用的是局部锁
1.7 之前Concurrencymap本身维护了一个Segment的数组,每一个segment可以看做一个hashmap。 Segment 里定义了HashEntry 和 HashMap里的HashEntry一样是用来存放数据的。在put的时候 通过key找到对应的segment,并且在执行Put方法的时候会把这个segement加锁,而不影响其他的segement。 因为Segement这个class继承了ReentrantLock的trylock()方法来尝试获取锁,如果能锁上就直接插入数值,如果数据已经被锁了,则尝试 然后等待。
1.8之后, Concurrencymap用node 替代了HashEntry来存储数据,并且增加了常量TREEIFY_THRESHOLD 用于判断是否需要将链表转换为红黑树的阈值。数组长度大于64,链表长度大于8就转变成红黑树。红黑树节点长度小于6退回链表
上锁的过程是使用了synchronized替代了ReentrantLock。 在执行put操作的时候,用synchronized 互斥锁,给node资源上锁。 Node有validate,使其保证内存可见性,使得这个值保持更新。
Mysql 用b+tree的原因是,
因为InnoDB的最小存储单位页,它的大小4,8,16K,根据设置,Btree的数据结构是可在一次IO读取 取得尽量多的数据,而不像AVLtree 或者红黑树,浪费资源。B+tree 相对Btree, 它的所有的数据都存在根节点,且每个根节点有双向链表相连可以范围检索。同时因为只在根节点存储数据,那么单次IO的在固定大小的情况下,取得的数据量更多,相对的IO次数,比BTREE会减少。
建立索引的原则
表的主键、外键必须有索引;主键具有唯一性,索引值也是有唯一的,查询时可以快速定位到数据行;外键一般关联的是另一个表的主键,所以在多表查询时也可以快速定位
记录数超过300行的表应该有索引;如果没有索引,需要把表遍历一遍,会严重影响数据库的性能
经常与其他表进行连接的表,在连接字段上应该建立索引
唯一性太差的字段不适合建立索引,并不能提升查询速度,反而会变慢
更新太频繁地字段不适合创建索引;在表中进行增、删、改、查时,索引也会有响应操作产生;字段更新的过于频繁,会导致对于系统资源的过多占用
经常出现在 where 子句中的字段,特别是大表的字段,应该建立索引
索引应该建在选择性高的字段上;如果很少的字段拥有相同值,即有很多独特之,则选择性很高。离散度低的,数据命中次数多,造成过多的IO操作,影响效率。
索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引
Mysql性能优化策略
Explain语法:explain select … from … [where ...]
例如:explain select * from news;
输出:
+----+-------------+-------+-------+-------------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+-------------------+---------+---------+-------+------+-------+
4、type:这列最重要,显示了连接使用了哪种类别,有无使用索引,是使用Explain命令分析性能瓶颈的关键项之一。
结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,得保证查询至少达到range级别,最好能达到ref,否则就可能会出现性能问题。
缓存 三种读写方式(读写数据库一致性)
1. Cache aside (旁路缓存)
查询:应用程序先去cache中读取数据,如果可以命中,则直接返回;如果没有命中,则去数据库中查询,成功后存入缓存。
更新:应用程序先去更新数据库,成功后再删除缓存
2.Read /write through (读写穿透)
存储服务收到业务应用的写请求时,会首先查 cache,如果数据在 cache 中不存在,则只更新 DB,如果数据在 cache 中存在,则先更新 cache,然后更新 DB。而存储服务收到读请求时,如果命中 cache 直接返回,否则先从 DB 加载,回种到 cache 后返回响应。
3.write behind caching (异步缓存写入)
在该模式下,更新数据时只更新缓存,不直接更新DB,通过异步的方式将缓存结果批量的或合并后再更新到DB。这种方式的特点是效率非常高,但不是强一致,甚至会丢失数据,适用于访问量、点赞数等对于性能要求高,但一致性要求不高的场景。
ES的知识点 elasticsearch 倒排索引原理
ES比Mysql查询效率高的原因:
相对于mysql的倒排索引,es多了一个包含了索引前缀的trie树数据结构,在检索的时候可以根据trie树更快的找到索引结果。Term -> term dictionary
NLP项目回忆
执行流程是: 数据导入,分词,特征提取(有一个词库,提取相关),分类(用了SVM 和 navie Bays(朴素贝叶斯)算法模型)。
Spring与Springboot相关
传统Spring框架存在的弊端:
Spring事物管理,MVC,启用第三方库都需要XML或Java进行显示配置,配置过重
写配置挤占了实际写应用的逻辑的时间
项目依赖管理,要考虑用那些库,还要知道哪些版本和库不会有冲突,影响开发效率
SpringBoot的优势:
自动配置:针对很多Spring常见的应用功能,SpringBoot能自动提供相关配置
起步依赖:告诉SpringBoot需要什么功能,它就能引入需要的库
CLI命令行界面:通过SpringBootCLI,借此你只需写代码就能完成完整的应用程序,无须传统项目构建
Spring 相关
@autowire 和@Resource的区别
Spring解决循环依赖的方法
在获取单例Bean的时候,会先从一级缓存singletonObjects里获取,如果没有获取到(说明不存在或没有实例化完成),会去第二级缓存earlySingletonObjects中去找,如果还是没有找到的话,就会三级缓存中获取单例工厂singletonFactory,通过从singletonFactory中获取正在创建中的引用,将singletonFactory存储在earlySingletonObjects 二级缓存中,这样就将创建中的单例引用从三级缓存中升级到了二级缓存中,二级缓存earlySingletonObjects,是会提前暴露已完成构造,还可以执行属性注入的单例bean的。 这个时候如何还有其他的bean也是需要属性注入,那么就可以直接从earlySingletonObjects中获取了。
Spring事务@Transitional失效的 8 大原因
Spring事务@Transitional失效的 8 大原因
数据库引擎不支持事务
没有被 Spring 管理
方法不是 public 的
自身调用问题,一个非事务的方法,调用了一个事务方法
不支持事务,使用了Propagation.NOT_SUPPORTED的事务传播属性
抛出异常,且异常没有被收集抛出,或者抛出的异常不是回滚的异常类,默认的是RuntimeExcption
synchronized 和 voliate 的区别
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住
Synchronized 和 lock的区别
从功能角度来看,Lock和Synchronized都是Java中用来解决线程安全问题的工具。
2. 从特性来看,
1. Synchronized是Java中的同步关键字,Lock是J.U.C包中提供的接口,这个接口有很多实现类,其中就包括ReentrantLock重入锁
2. Synchronized可以通过两种方式来控制锁的粒度 , 一种是把synchronized关键字修饰在方法层面,另一种是修饰在代码块上,并且我们可以通过Synchronized加锁对象的声明周期来控制锁的作用范围,比如锁对象是静态对象或者类对象,那么这个锁就是全局锁。
如果锁对象是普通实例对象,那这个锁的范围取决于这个实例的声明周期。
Lock锁的粒度是通过它里面提供的`lock()`和`unlock()`方法决定的,包裹在这两个方法之间的代码能够保证线程安全性。而锁的作用域取决于Lock实例的生命周期。
3. Lock比Synchronized的灵活性更高,Lock可以自主决定什么时候加锁,什么时候释放锁,只需要调用`lock()`和`unlock()`这两个方法就行,同时Lock还提供了非阻塞的竞争锁方法`tryLock()`方法,这个方法通过返回true/false来告诉当前线程是否已经有其他线程正在使用锁。
Synchronized由于是关键字,所以它无法实现非阻塞竞争锁的方法,另外,Synchronized锁的释放是被动的,就是当Synchronized同步代码块执行完以后或者代码出现异常时才会释放。
4. Lock提供了公平锁和非公平锁的机制,公平锁是指线程竞争锁资源时,如果已经有其他线程正在排队等待锁释放,那么当前竞争锁资源的线程无法插队。而非公平锁,就是不管是否有线程在排队等待锁,它都会尝试去竞争一次锁。 Synchronized只提供了一种非公平锁的实现。
3. 从性能方面来看,Synchronized和Lock在性能方面相差不大,在实现上会有一些区别,Synchronized引入了偏向锁、轻量级锁、重量级锁以及锁升级的方式来优化加锁的性能,而Lock中则用到了自旋锁的方式来实现性能优化。
常见Mysql的慢查询优化方式
- 开启慢查询日志
- 直接分析mysql慢查询日志 ,利用explain关键字可以模拟优化器执行SQL查询语句,来分析sql慢查询语句
- 分析sql语句
- 查看索引是不是没起到作用,例如也有like ‘%abc’或者隐式的变量类型转换。
- 优化表结构,如果字段太多,也会影响查询效率。
- 将一个大的查询分解为多个小查询是很有必要的。
- 优化具体的sql语句
- 优化limit分页
例子:select id,title from collect limit 90000,10; ->
先查询出主键id值
select id,title from collect where id>=(select id from collect order by id limit 90000,1) limit 10;
原理:先查询出90000条数据对应的主键id的值,然后直接通过该id的值直接查询该id后面的数据。
Spring中Bean的作用域
默认情况下,Bean在Spring容器中是单例的(singleton),可以通过@Scope注解或者配置文件中的scope属性设置Bean的作用域
①singleton:在Spring容器中仅存在一个这个Bean的实例,即一个Bean定义对应一个对象实例。
②prototype:一个bean定义对应多个对象实例。每次调用StringBeanUtil.getBean()时,都会执行new操作,返回一个新的Bean实例。
③request:每次Http请求都会创建一个新的Bean,该作用域仅在当前HTTP Request内有效。
④session:同一个Http Session共享一个Bean,不同Http Session使用不同的Bean。该作用域仅在当前 HTTP Session 内有效。
⑤ global session :在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。
MySql的事务隔离级别
三种并发情况: 脏读,不可重复读,幻读
脏读: 读取到 未提交的数据
不可重复读: 一个事物读取到提交过的数据,
幻读: 一个事物读取到,读取范围,多了 另一个事物插入的数据
对应四种隔离级别
未提交读(read-uncommited)
提交读 (read-commited)
可重复读(read-repeated)
串行化 (serializable)
使用间隔锁,来解决幻读。
Redis持久化
https://www.cnblogs.com/itdragon/p/7906481.html
AOF
RDB 内存快照(默认开启)
触发条件:
1 在指定的时间间隔内,执行指定次数的写操作
2 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
3 执行flushall 命令,清空数据库所有数据,意义不大。
4 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义...也不大。
通过RDB文件恢复数据:
将dump.rdb 文件拷贝到redis的安装目录的bin目录下,重启redis服务即可。在实际开发中,一般会考虑到物理机硬盘损坏情况,选择备份dump.rdb 。可以从下面的操作演示中可以体会到。
RDB 的优缺点
优点:
1 适合大规模的数据恢复。
2 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。
缺点:
1 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。
2 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。
所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。
Mysql死锁发生条件
事物占用资源的同时,请求另一个被占用的资源,就发生死锁。
JAVA 死锁的四个必要条件
互斥条件:一个资源同时只能被一个线程所使用.
请求与保持条件: 例如请求第二把锁的时候, 保持自身的第一把锁不去释放.
不剥夺条件: 进程已获得的资源(例如已经获得的锁),没有外界的力量来去剥夺这把锁.
循环等待条件: 两个线程时, 是你等我释放锁, 我等你释放锁. 多个线程时, 是头尾相接的等待.
JVM 内部结构
内部结构是分为线程共享堆(GC),线程私有程序计数器,虚拟机栈,本地方法栈,曾经有方法区,后来变成meta space直接有内存管理,而不是JVM。
内存空间:是在JVM运行的时候操作所分配的内存区。运行时内存主要可以划分为以下几个区域:
方法区(Method Area):是各个线程共享的区域,存储类结构信息的地方,包括常量池、静态变量、构造函数等。虽然JVM规范把方法区描述为堆的一个逻辑部分, 但它却有个别名non-heap(非堆)。方法区还包含一个运行时常量池。
Java堆(Heap):也是线程共享的区域,存储Java实例或者对象的地方。 这里GC的主要区域。堆的内存空间既可以固定大小,也可以在运行时动态地调整。通过参数-Xms(memory start)和-Xmx(memory max)来进行设置。如果二者设置一样,则堆内存被固定。
堆分成两块:新生代和老生代。老生代接收新生代在一定规则下依旧存活的对象,也接纳在新生代无法容纳的超大对象。新生代 = 1个Eden区 + 2个Survivor区。当Eden区装填满时,会出发Young Garbage Collection,即YGC。
每个对象都会有一个阈值,每次YGC都会加1,当达到设置好的阈值时,便会将对象晋升到老年代中。当然如果YGC移动的对象过大,也会直接将其晋升到老生代中,如果老生代此时现有的内存不够存放该对象,则会出发Full Garbage Collection,即FGC。如果依然放不下,则抛出OOM。
虚拟机栈Java栈(Stack):每个线程私有的区域,它的生命周期与线程相同。每执行一个方法就会往栈中压入一个元素,这个元素叫栈帧,用于存储局部变量表、操作栈、方法返回值等。每一个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。栈帧在整个JVM体系中的地位颇高,包括局部变量表、操作栈、动态连接、方法返回地址等。
局部变量表:存放方法参数和局部变量的区域。如果是非静态方法,则在index[0]位置上存储的是方法所属对象的实例引用,随后存储的是参数和局部变量。
操作栈:一个初始状态为空的桶式结构栈。JVM的执行引擎是基于栈的执行引擎,这里的栈指的其实就是操作栈。
本地方法栈(Native Method Stack):和Java栈类似,只不过是为JVM使用到的native方法服务的。本地方法可以通过JNI(Java Native Interface)来访问虚拟机运行时的数据区,甚至可以调用寄存器,具有和JVM相同的能力和权限。
程序寄数器(PC Register):每个线程私有的区域,用于保存当前线程执行的内存地址。由于JVM程序是多线程执行的(线程轮流切换),所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前中断的地方。还有程序该怎么执行,哪个方法先执行,哪个方法后执行,这些指令执行的顺序就是程序寄数器在管,它的作用就是控制程序指令的执行顺序。 执行引擎就是根据程序寄数器调配的指令顺序,依次执行程序指令。
HTTP协议的组成部分
HTTP 请求的组成
状态行、请求头、消息主体三部分组成。
HTTP 响应的组成
状态行、响应头、响应正文
四种线程池的种类 和实现方法
1. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++) {
final int index = i;
singleThreadExecutor.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("newSingleThreadExecutor: " + index);
Thread.sleep(2 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
singleThreadExecutor.shutdown();
2.newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池的规模不存在限制。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//1- 在未来某个时间执行给定的命令。
// 该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
//2- 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
// 该 Future 的 get 方法在成功完成时将会返回给定的结果
cachedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
cachedThreadPool.shutdown();
3. newFixedThreadPool 创建一个固定长度的线程池,超过最大的数量会在队列中等待。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++)
{
final int index = i;
//1- 在未来某个时间执行给定的命令。
// 该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
threadRunMethod(index);
}
});
//2- 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
// 该 Future 的 get 方法在成功完成时将会返回给定的结果
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
threadRunMethod(index);
}
});
}
fixedThreadPool.shutdown();
4. NewScheduleThreadPool 创建一个固定长度的threadpool, 周期性的执行任务
/**
*
* 跟 testScheduleAtFixedRate 非常类似,就是延迟的时间有点区别
* 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;
* 也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,
* 接着在 initialDelay + 2 * period 后执行,依此类推。
*
* 如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
* 推迟的时间 : 等到上次的任务执行完之后再延迟period 的时间后执行。
* @param scheduledExecutorService
*/
private static void testScheduleWithFixedDelay(ScheduledExecutorService scheduledExecutorService) {
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
System.out.println("延迟2秒,再3秒执行一次");
//如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
//本次任务执行完后下次的任务还需要延迟period时间后再执行
Thread.sleep(6*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},2,3,TimeUnit.SECONDS);
}
/**
* 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;
* 也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,
* 接着在 initialDelay + 2 * period 后执行,依此类推。
*
* 如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
* 推迟的时间 : 等到上次的任务执行完就立马执行。
* @param scheduledExecutorService
*/
private static void testScheduleAtFixedRate(ScheduledExecutorService scheduledExecutorService) {
scheduledExecutorService.scheduleAtFixedRate(() -> {
try {
System.out.println("延迟2秒,再3秒执行一次");
//如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
//如果任务里面执行的时间大于 period 的时间,本次任务执行完后,下次任务立马执行。
Thread.sleep(6*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},2,3,TimeUnit.SECONDS);
}
/**
* 创建并执行在给定延迟后启用的一次性操作
* @param scheduledExecutorService
*/
private static void testSchedule(ScheduledExecutorService scheduledExecutorService) {
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
}
分布式系统的简单定义:
就是将单体应用的各个系统,独立出来成为一个个独立的子系统所组成的系统应用可以被成为分布式系统。
一个完美的单例模式(非enmu)
//使用volatile关键字 避免指令重排
private static volatile Singleton singleton = null;
//私有化无参构造与增加判断,避免反射形式破坏单例
private Singleton(){
if(singleton != null){
throw new RuntimeException("can not do this");
}
}
//DLC的方法 写getInstance方法
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
//增加readResolve方法,避免以序列化与反序列化的方式破坏单例
private Object readResolve(){
return singleton;
}
//重写clone(),避免clone的方式破坏单例
@Override
public Object clone() throws CloneNotSupportedException {
return singleton;
}
Spring事务传播属性
在 spring的 TransactionDefinition接口中一共定义了六种事务传播属性:
PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作
线程间通信
线程间通信存在三种方式,资源共享,消息传递,管道流
- 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。
volatile共享内存
- 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。
wait/notify等待通知方式
join方式
- 管道流
管道输入/输出流的形式
new 一个空的object会占多少内存
假设一个Object class如下
Class NewObject {
int count;
boolean flag;
Object ob;
}
那么在初始化之后,会占用多少空间呢?
其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因为Java在对对象内存分配时都是以8的整数倍来分,因此大于17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。
在堆中,每个对象由4各部分组成,对象头,基本类型域占用的空间,引用类型域占用的空间,填充空间。
对象头占用空间:
- 一个普通对象,占用8 bytes
- 数组,占用 12 bytes,包含普通对象的 8 bytes + 4 bytes
基本类型:boolean、byte 占用 1 byte,char、short 占用 2 bytes,int、float 占用 4 bytes,long、double 占用 8 byte
引用类型:引用类型 占4byte
填充空间:每个对象占用的总空间是以8的倍数计算的,所以为了满足8的倍数,剩余空间补齐。
什么事聚集索引,非聚集索引
聚集索引 就是基于主键创造的索引,除了主键以外的索引,统称为非聚集索引。
在InnoDB中,一张表对应的物理文件,本身就是按照B+树组织。聚集索引就是表的主键来构建的B+树,叶子节点里存储了对应的每一行的数据。
所以这种情况下,在InnoDB中, 聚集索引不仅是一种所以类型,也是一种数据的数据存储方式。所以每一张表必须有一个主键,如果没有定义主键,
在建立表的时候,会建立一个隐藏的列作为主键索引来储存数据行。
建立索引的时候,一个自增ID来作为索引,这样建立的索引的时候会有一个连续自增的属性,按照顺序存储在磁盘上,减少IO,提高性能。
Mybatis里${} 和#{}的区别
都是动态传参的一种方式, 可以讲参数传入到XML文件当中。#号占位符等于一个JDBC当中的?占位符,等于向prepareStatem预处理语句中 传递参数。
而prepareStatement里会预编译传入的sql语句,sql语句中的占位符,规定了sql语句的结构,在设置参数的时候,如果有特殊字符会自动进行转译,可以防止sql注入。
而${},在sql语句当中直接进行拼接,所以无法防止sql注入
${}适合使用在动态传入表明,动态传入列名,根据列名排序等。
Bean生命周期:
大致分为五个阶段:
1.创建前准备,在bean开始加载前,从上下文和配置中,解析和查找bean扩展功能的方法,比如init-method, destroyMethod,BeanFactoryPostPrcessor等这一类Bean加载当中前置后置的方法和类
2.创建实例,执行bean构造器,暴露但是未实例化,通过反射创造bean的实例对象,并且扫描和解析bean声明的一些属性。
3.依赖注入, 被实例化的Bean,如果内部有Autowire 或者Resource等注解,存在这些依赖,则会对依赖进行注入,并对一些扩展类进行调用,如刚才说的BeanFactoryPostProcessor类前置方法,
以及调用实现了Aware接口类的实现方法
4.容器缓存。 主要是把实例化的bean放入容器一个concurrenthashmap当中,已经可以被使用了,调用Init-method等方法,BeanFactoryPostProcessor 后置方法postProcessAfterInitialization
5.销毁,销毁实例阶段,当spring的应用上下文被关闭时,这个上下文的所有bean都会销毁,如果存在bean实现了DisposableBean接口,或者配置了destroyMethod,在这个阶段会调用相应的方法。
初始化就是把bean的元数据准备好的过程,实例化是使用元数据实例化(newInstance)的过程
BeanFactory和FactoryBean的区别
BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,
FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式(如果想了解装饰模式参考:修饰者模式(装饰者模式,Decoration) 我们可以在getObject()方法中灵活配置。其实在Spring源码中有很多FactoryBean的实现类.
区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
SpringBoot 自定义starter
1.定义@ConfigurationProperty 定义一个实体类映射配置信息。
2.定义一个Service,完成实际的逻辑。
3.定义一个配置类
▲ @Configuration 使用Configuration。
▲ @EnableConfigurationProperties 注解。该注解是用来开启对3步骤中 @ConfigurationProperties 注解配置Bean的支持。也就是@EnableConfigurationProperties注解告诉Spring Boot 能支持@ConfigurationProperties。
也可以在 @ConfigurationProperties 注解的类上添加 @Configuration 或者 @Component 注解
▲ @ConditionalOnProperty 注解控制 @Configuration 是否生效。简单来说也就是我们可以通过在yml配置文件中控制 @Configuration 注解的配置类是否生效。
4. 在resource文件夹,新建spring.factories文件, 在该文件中讲配置类用的包名和类名配置,该配置指定上步骤中定义的配置类为自动装配的配置
1 #-------starter自动装配---------
2 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.starter.config.DemoConfig
SpringBoot应用篇(一):自定义starter_Mr小林的博客-CSDN博客
这样就可以在pom文件中引入依赖,在properties文件中通过配置文件配置参数。
如何让主线程等待所有的子线程执行结束之后再执行
1.用子线程调用join方法 阻塞主线程。
2.用线程池的isTerminated方法,当调用shutdown()方法后,并且所有提交的任务完成后才会返回为true
3.用Future 的get方法,阻塞性的取得结果,//get方法为阻塞获取
4.用CountDownLatch,初始化定义计数器次数,使用latch.await() 方法阻塞线程
每调用一次countDown方法,计数器会减1,在计数器减为0之前,await方法将会阻塞主线程。
SpringMVC的工作流程
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
- DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
- HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
- ViewResolver 会根据逻辑 View 查找实际的 View。
- DispaterServlet 把返回的 Model 传给 View(视图渲染)。
- 把 View 返回给请求者(浏览器)
SpringBoot 自动装配原理
SpringBoot启动的时候通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中所有的自动配置类,并对其进行加载,而这些自动配置类的类名都是以AutoConfiguration结尾来命名的,它实际上就是一个javaConfig形式的Spring容器配置类,它们都有一个@EnableConfigurationPerperties的注解,通过这个注解启动XXXProperties命名的类去加载全局配置中的属性,如server.port,而XXXProperties通过@ConfigurationProperties注解将全局配置文件中的属性与自己的属性进行绑定。