回表
InnoDB的索引分为两种:聚簇索引和普通索引
像我们了解到的关于InnoDB存储引擎的知识,底层是基于B+树的,数据都存储于叶子节点,因此InnoDB引擎之下有且只有一个聚簇索引,也就是主键,如果没有主键的话,默认第一个非空unique列为聚簇索引。所以我们根据这个聚簇索引查表很快。
另外一点就是,聚簇索引的叶子节点存储的是行记录,普通索引存储主键值。
那么,什么是回表
简单来说,我们知道有一个聚簇索引和一个普通索引,索引的底层都是B+树,我们通过索引去寻找值的时候,要走索引树,案例来说我们走一棵索引树就可以,但是回表会走两边索引树,原因就是第一棵索引树不能找到我们要的确定的值,才会产生回表。
假如我们有这样一张表
里面数据如下:
我们知道在InnoDB中,聚簇索引就是主键索引,绘制索引树如下:
而普通索引的索引树如下:
然后比如我们执行这句话:select * from t where name='lisi';
这个时候会走普通索引,但是普通索引并不能锁定所有我们想要的数据,所以了能找到这些数据,就会进行回表:
(1)先通过普通索引定位到主键值id=5;
(2)在通过聚集索引定位到行记录;
自定义的线程池拒绝策略
重写rejectedExecution方法
有界队列和无界队列
Java中有哪些无界队列和有界队列?
常见的有界队列为
ArrayBlockingQueue 基于数组实现的阻塞队列
LinkedBlockingQueue 其实也是有界队列,但是不设置大小时就时 Integer.MAX_VALUE,内部是基于链表实现的
ArrayBlockingQueue 与 LinkedBlockingQueue 对比一哈
ArrayBlockingQueue 实现简单,表现稳定,添加和删除使用同一个锁,通常性能不如后者
LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些
SynchronousQueue 比较奇葩,内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用 队列来实现公平性的调度,使用栈来实现非公平的调度,在 Java6 时替换了原来的锁逻辑,使用 CAS 代替了
上面三个队列他们也是存在共性的
put take 操作都是阻塞的
offer poll 操作不是阻塞的,offer 队列满了会返回 false 不会阻塞,poll 队列为空时会返回 null 不会阻塞
补充一点,并不是在所有场景下,非阻塞都是好的,阻塞代表着不占用 CPU,在有些场景也是需要阻塞的,put take 存在必有其存在的必然性
常见的无界队列
ConcurrentLinkedQueue 无锁队列,底层使用 CAS 操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如 ArrayList 等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常
PriorityBlockingQueue 具有优先级的阻塞队列
DelayedQueue 延时队列,使用场景
缓存:清掉缓存中超时的缓存数据
任务超时处理
补充:内部实现其实是采用带时间的优先队列,可重入锁,优化阻塞通知的线程元素 leader
LinkedTransferQueue 简单的说也是进行线程间数据交换的利器,在 SynchronousQueue 中就有所体现,并且并发大神 Doug Lea 对其进行了极致的优化,使用 15 个对象填充,加上本身 4 字节,总共 64 字节就可以避免缓存行中的伪共享问题,其实现细节较为复杂,可以说一下大致过程:
比如消费者线程从一个队列中取元素,发现队列为空,他就生成一个空元素放入队列 , 所谓空元素就是数据项字段为空。然后消费者线程在这个字段上旅转等待。这叫保留。直到一个生产者线程意欲向队例中放入一个元素,这里他发现最前面的元素的数据项字段为 NULL,他就直接把自已数据填充到这个元素中,即完成了元素的传送。大体是这个意思,这种方式优美了完成了线程之间的高效协作。参考自
https://blog.csdn.net/u013851082/article/details/70140728
现在也来说一说无界队列的共同点
put 操作永远都不会阻塞,空间限制来源于系统资源的限制
底层都使用 CAS 无锁编程
为什么生产上不能使用无界队列?
使用的是无界队列,如果并发任务量巨大,任务逻辑比较耗时,未来得及处理的任务会大量堆积在队列里,导致内存急速飙高,可能导致程序挂掉。
解决方法可以考虑设置一下巧妙的拒绝策略
线程是如何通信的
1.volatile,将缓存直接写入到主内存
2.synchronized,释放锁的时候直接释放到主内存
3.await notify notifyAll
先给A(累加)线程加锁,进入await之后会让线程沉睡,等待signal信号来叫醒,这是A线程解锁后会进入沉睡,运行B线程;b线程先加锁然后进行递减,当值为0值也会进行也会睡眠的,然后解锁,把锁给A。就这样来进行通信的。
4.AQS(BlockingQueue,Lock,信号量,CountDownLatch,CyclicBarrier)
Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。一般用于控制并发线程数,及线程间互斥。另外重入锁 ReentrantLock 也可以实现该功能,但实现上要复杂些。
功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会。
单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
还可以用pipe,基于nio的通信方式
Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。一进一出。先作为初步了解怎么使用。
值得注意的是该类在java.nio.channels下,说明该类属于nio方式的数据通信方式,那就使用Buffer来缓冲数据。
Pipe就是个空管子,这个空管子一头可以从管子里往外读,一头可以往管子里写
操作流程:
1.首先要有一个对象往这个空管子里面写。写到哪里呢?这个空管子是有一点空间的,就在这个管子里。
写的时候就是写到管子本身包含的这段空间里的。这段空间大小是1024个字节。
2.然后另一个对象才能将这个装满了的管子里的内容读出来。
网络
arp的原理
我们以主机A(192.168.1.5)向主机B(192.168.1.1)发送数据为例。当发送数据时,主机A会在自己的ARP缓存表中寻找是否有目标IP地址。如果找到了,也就知道了目标MAC地址,直接把目标MAC地址写入帧里面发送就可以了;如果在ARP缓存表中没有找到相对应的IP地址,主机A就会在网络上发送一个广播,目标MAC地址是“FF.FF.FF.FF.FF.FF”,这表示向同一网段内的所有主机发出这样的询问:“192.168.1.1的MAC地址是什么?”网络上其他主机并不响应ARP询问,只有主机B接收到这个帧时,才向主机A做出这样的回应:“192.168.1.1的MAC地址是00-aa-00-62-c6-09”。这样,主机A就知道了主机B的MAC地址,它就可以向主机B发送信息了。同时它还更新了自己的ARP缓存表,下次再向主机B发送信息时,直接从ARP缓存表里查找就可以了。ARP缓存表采用了老化机制,在一段时间内如果表中的某一行没有使用,就会被删除,这样可以大大减少ARP缓存表的长度,加快查询速度。
Spring
Spring七种事务传播机制的关键字对应的意思都是啥
propagation_required
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
propagation_supports
支持当前事务,如果当前没有事务,就以非事务方式执行。
propagation_mandatory
使用当前的事务,如果当前没有事务,就抛出异常。
propagation_requires_new
新建事务,如果当前存在事务,把当前事务挂起。
propagation_not_supported
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
propagation_never
以非事务方式执行,如果当前存在事务,则抛出异常。
propagation_nested
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。