Arraylist与LinkedList默认空间是多少;
ArrayList的构造方法的源码:
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}
默认空间是10;
private transient Entry
private transient int size = 0;
默认空间是0;
Arraylist与LinkedList区别与各自的优势;
1.ArrayList是实现基于动态数组的数据结构,LinkedList基于链表的数据结构
2.对于随机访问的get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针
3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动元素
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
List 和 Map 区别;
List是有顺序的 可重复的
Map是通过键值对进行取值的 key和value是一一对应的
这张图简单揭示了Set、List与Map之间的相对关系。
需要说明下的是,图中的实现并不指这么简单的实现,这个稍后会说到。
heap 和 stack 的区别
stack: 先进后出
函数调用栈,有结构,查询快,线程独占的,存储引用和基本类型
heap:
先进先出,没有结构,查询慢,线程共享的,存储数值,等待垃圾回收
谈谈HashMap,哈希表解决hash冲突的方法;
hashCode的实现源码:
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
为什么要重写hashcode()和equals()以及他们之间的区别与关系;
情景一:两对象根据算法的实现要求本来哈希值应该相等,但是因调用父类的hashcode方法,也许就不等了,这与原理就相违背了;
情景二,例如在HashMap中,根据indexFor(hash, table.length)可以直接将Entry定位到buckt的具体指针处。
通常想查找一个集合中是否包含某个对象,就是逐一取出每个元素与要查找的元素进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则返回否定的信息,如果一个集合中有很多元素譬如成千上万的元素,并且没有包含要查找的对象时,则意味着你的程序需要从该集合中取出成千上万个元素进行逐一比较才能得到结论,于是,有人就发明了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域.
hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
hashCode方法的作用:
对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在java中也一样,hashCode方法的主要内容是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。
equals也可以进行比较,但如果业务场景中数据比较大,则会影响性能。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashCode的值,实际上在HashMap的具体实现中会用到一个table保存已经存进去的对象的hashCode的值,如果table中没有该hashCode的值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashCode值,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列到其它地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确实是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里看,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。
不能直接根据hashCode值判断两个对象是否相等,因为不同的对象可能会生成相同的hashCode值。虽然不能根据hashCode值判断两个对象是否相等,但是可以直接根据hashCode值判断两个对象不等,则必定是两个不同的对象。如果要判断两个对象是否相等,必须通过equals方法。
也就是说两个对象如果调用equals方法得到的结果为true,则两个对象的hashCode值必定相等;
如果equals方法得到的结果为false,则两个对象的hashCode值不一定不同;
如果两个对象的hashCode值不等,则equals方法得到的结果必定为false;
如果两个对象的hashCode值不等,则equals方法得到的结果未知。
在HashMap进行get操作时,因为得到的hashCode值不同(虽然两个对象存储地址不同也有可能得到相同的hashCode的值),所以导致在get方法中for循环不会执行,直接返回null。
在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
如果两个对象根据equals方法比较是不等的,则hashCode方法不一定返回不同的整数。
在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段
hashCode是jdk中根据对象的地址算出来的一个int数字,即对象的哈希码值,代表了该对象在内存中的存储位置。hashCode()方法是顶级类Object类的提供一个方法,所有的类都可以进行对hashCode方法重写。在比较一个类是否相同时,往往会重写equals方法,值得注意的是,重写equals方法的同时也要重写hashCode方法,多次调用一个对象的hashCode方法必须返回一个数字,这也是必须遵守的一个规范。
hash冲突:
当两个对象equals相同,hashCode规定也必须相同,但反过来不一定,两个对象对应一个hashCode,但equals却并不相等。这就是传说中的hash冲突。HashMap是以hashCode取模数组形式存放值的,那两个对象hashCode一样会不会造成前一个对象的覆盖呢?答案是不会的,因为它采用了另外一种链表数据结构来解决hash冲突,即使两个对象的hashCode一样,它们会放到当前数组索引位置的链表中。
HashSet通过HashMap来实现的,用来存储不重复数据的,怎么判断里面的对象是否重复呢?判断对象是否重复即是判断对象里面的属性是否都一样,这时必须是重写了equals方法去比较对象里所有的值,而不是比较引用地址,比较引用地址它们永远都不相等,除非是同一个对象。通过equals比较的过程性能是非常不佳的,所以有了hashCode这个设计,简单两个数字的比较性equals没法比的,所以可以先通过比较对象的hashCode是否一样确定是不同一个对象,如果hashCode不一样这时肯定就不是同一对象,反之如果hashCode一样而且equals或者==也一样这肯定就是同一对象。所以先比较数字的hashCode再比较equals或者==,这样效率会明显提升。
假如重写了equals而不重写hashCode方法,多个对象属性值一样的它们的hashCode肯定是不一样的,这时作为key在put到map中的时候,就会有多个这样的key,而达不到对象作为key的场景,同样也达不到HashSet去重的效果。
Object的hashcode()是怎么计算的?
若hashcode方法永远返回1或者一个常量会产生什么结果?
https://blog.csdn.net/cnq2328/article/details/50436175
Java Collections和Arrays的sort方法默认的排序方法是什么;
https://blog.csdn.net/timheath/article/details/68930482
引用计数法与GC Root可达性分析法区别;
http://baijiahao.baidu.com/s?id=1583441733083989684&wfr=spider&for=pc
浅拷贝和深拷贝的区别;
https://blog.csdn.net/wangxueming/article/details/52034841
String s="abc"和String s=new String("abc")区别;
https://blog.csdn.net/u010644448/article/details/51980370
HashSet方法里面的hashcode存在哪,如果重写equals不重写hashcode会怎么样?
https://blog.csdn.net/gewuban/article/details/52484356
HashSet和TreeSet的区别;
HashSet是按照哈希来存取元素的,因此速度较快。HashSet继承自抽象类AbstractSet,然后实现了Set、Cloneable、Serializable接口。
TreeSet也是继承自AbstractSet,不过不同的是其实现的是NavigableSet接口。而NavigableSet继承自SortedSet。SortedSet是一个有序的集合。其添加的元素必须实现了Comparable接口,因为其在添加一个元素的时候需要进行排序。NavigableSet则提供了更多的有关元素次序的方法,
反射的作用与实现原理;
https://blog.csdn.net/WildGrasses/article/details/70243342
https://blog.csdn.net/winy_lm/article/details/49470355
Java中的回调机制;
https://www.cnblogs.com/xrq730/p/6424471.html
https://blog.csdn.net/caihongdao123/article/details/51657840
模板方法模式;
https://blog.csdn.net/eson_15/article/details/51323902
开闭原则说一下;
https://blog.csdn.net/hfreeman2008/article/details/52344022
发布/订阅使用场景;
redis的发布订阅场景
https://blog.csdn.net/fly910905/article/details/78495971
JMM里边的原子性、可见性、有序性是如何体现出来的,JMM中内存屏障是什么意思,
运行异常和一般异常的区别?
Error:表示由JVM所侦测到的无法预期的错误,由于这是属于JVM层次的严重错误,导致JVM无法继续执行,因此,这是不可捕捉到的,无法采取任何恢复的操作,顶多只能显示错误信息。Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形.应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出).假如出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的.
Exception:表示可恢复的例外,这是可捕捉到的。
Java提供了两类主要的异常:runtime exception和checked exception。checked 异常也就是我们经常遇到的IO异常,以及SQL异常都是这种异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。这类异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误.
但是另外一种异常:runtime exception,也称运行时异常,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。RuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等.处理RuntimeException的原则是:假如出现RuntimeException,那么一定是程序员的错误.例如,可以通过检查数组下标和数组边界来避免数组越界访问异常.
出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类,也有一般异常的特点,是可以被Catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
如果不想终止,则必须扑捉所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。在这个场景这样处理可能是一个比较好的应用,但并不代表在所有的场景你都应该如此。如果在其它场景,遇到了一些错误,如果退出程序比较好,这时你就可以不太理会运行时异常,或者是通过对异常的处理显式的控制程序退出。异常处理的目标之一就是为了把程序从异常中恢复出来。
AtomicInteger底层实现原理;
CAS原理和自旋锁原理
synchronized与ReentraLock哪个是公平锁;
ReentraLock可以设置为公平锁
CAS机制会出现什么问题;
ABA问题
用过并发包下边的哪些类;
CountDownLatch:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用途:
将计数器1初始化的CountDownLatch用作一个简单的开/关锁存器,或入口:在通过调用countDown()的线程打开入口前,所有调用await的线程都等在入口处。
用N初始化的CountDownLatch可以使一个线程在N个线程完成某项操作之前一直等待,或者使其在某项操作完成N次之前一直等待。
不要求调用countDown方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个await。
CyclicBarrier:一个同步辅助类,它允许一组线程互相等待,直到达到某个公共屏障点。在涉及一组固定大小的线程的程序中,这些线程必须不时地相互等待。因为该barrier在释放等待线程后可以重用,所以它成为循环的barrier。
如果屏障操作在执行时不依赖于正挂起的线程,则线程组中的任何线程在获得释放时都能执行该操作。
内存中一致性的效果:线程中调用await()之间的操作,那些是屏障操作的一部分的操作,后者依次happen-before紧跟在从另一个线程中对应await()成功返回。
AbstractQueuedSynchronizer:
https://www.cnblogs.com/longshiyVip/p/5211298.html
http://raychase.iteye.com/blog/1998965
一个线程连着调用start两次会出现什么情况?
出现异常
wait方法能不能被重写,wait能不能被中断;
不能被重写,可以被中断
线程池的实现?四种线程池?重要参数及原理?任务拒接策略有哪几种?
拒绝策略1:将抛出 RejectedExecutionException.
策略2:ThreadPoolExecutor.CallerRunsPolicy
用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
策略3:
RejectedExecutionHandler handler =
new ThreadPoolExecutor.DiscardOldestPolicy();
策略4: ThreadPoolExecutor.DiscardPolicy常用的避免死锁方法;
1、避免一个线程同时获取多个锁
2、避免一个线程同时占用多个资源,尽量保证每个锁只占用一个资源
3、尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
4、对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
什么是乐观锁和悲观锁;
乐观锁:对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观苏:对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
三、JVM
为什么进行垃圾回收?什么时候触发GC?
垃圾回收(gc)的目的是释放堆中不需要保存的对象,达到内存的充分利用。
1.回收哪些对象的判定
垃圾回收最简单的思路是采用引用计数的方式,即记录对象被引用的次数,直到一段时间内对象都没有被其他对象引用,此时可以确定该对象能被回收,引用计数实现简单,运行高效,但是有一个循环引用的问题,即两个本应被回收的对象因为互相引用而无法被回收,针对这个问题又有了弱引用,即把两个互相引用的一个引用计数改为弱引用,弱引用不会使次数加1,c++即是这么做的。
jvm虚拟机的使用的是根寻路算法,其大致思想是看除堆区以外的内存区域能否通过引用链找到堆中的对象,找不到就证明该对象可以被回收。
程序员可以手动调用gc,一般是系统等到新生代的内存区占满了又需要分配内存的时候,这个时候新生代就变成了老年代,等老年代的内存占满之后开始回收老年代所占的内存区。
垃圾回收机制的要点:
1.垃圾回收线程是一个守护线程(指在程序运行的时候在后台提供一种通用服务的线程)
2.程序员可以主动申请垃圾回收,使用代码System.gc();但是由于垃圾回收是由JVM负责的,因此请求可能被JVM可以拒绝。
3.垃圾回收器的分类:串行垃圾回收器,并行垃圾回收器,G1垃圾回收器
4.回收机制:分代复制垃圾回收,增量回收
5.作用:防止内存泄漏。
当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用 情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式 确定哪些对象是"可达的",哪些对象是"不可达的"。当 GC 确定一些对象为"不可达" 时,GC 就有责任回收这些内存空间。这可以有效的防止内存溢出。
内存溢出是指:长生命周期的对象持有短生命周期的对象的引用,这会导致短生命周期的对象的所占堆内存无法被释放。
①比如把一些不怎么使用的对象存放到全局Map中,
②或者创建了内部类对象,而其外部类即使长时间没有使用也不能释放其内存空间。
Minor GC与Full GC分别在什么时候发生?什么时候触发Full GC;
Minor GC发生在新生代中的垃圾收集动作,采用复制算法;
Full GC是发生在老年代垃圾回收动作,采用标记-整理算法;
1system.gc();2.老年代空间不足,3.gc担保不足
GC收集器有哪些?CMS收集器与G1收集器的特点。Java在什么时候会出现内存泄漏;
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
Java中的大对象如何进行存储;
通过缓存进行存储
https://blog.csdn.net/zeratyl/article/details/79094482
自己写的类被什么加载,什么时间加载;
https://blog.csdn.net/lin353809836/article/details/69939400
为什么新生代内存需要有两个Survivor区?
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。你也许会问,执行时间长有什么坏处?频发的Full GC消耗的时间是非常可观的,这一点会影响大型程序的执行和响应速度,更不要说某些连接会因为超时发生连接错误了。
方案 | 优点 | 缺点 |
---|---|---|
增加老年代空间 | 更多存活对象才能填满老年代。降低Full GC频率 | 随着老年代空间加大,一旦发生Full GC,执行所需要的时间更长 |
减少老年代空间 | Full GC所需时间减少 | 老年代很快被存活对象填满,Full GC频率增加 |
显而易见,没有Survivor的话,上述两种解决方案都不能从根本上解决问题。
我们可以得到第一条结论:Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,下面我们来分析一下。
为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区。假设现在只有一个survivor区,我们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。
我绘制了一幅图来表明这个过程。其中色块代表对象,白色框分别代表Eden区(大)和Survivor区(小)。Eden区理所当然大一些,否则新建对象很快就导致Eden区满,进而触发Minor GC,有悖于初衷。
栈主要存的数据是什么,堆呢?
栈内存:线程是私有的,也就是说局部变量和方法是不可共享的。
堆内存:对象和数组是在堆内存中创建的,所有线程都可以访问,包括成员变量、静态变量和数组元素是可共享的;
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类 型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等 指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时 动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
堆分为哪几块,比如说新生代老生代,那么新生代又分为什么?
java垃圾收集管理器的主要区域,因此很多时候被称为“GC堆”。
分为新生代和老年代;
新生代分为:Eden和Survivor。
软引用和弱引用的使用场景(软引用可以实现缓存,弱引用可以用来在回调函数中防止内存泄露);
软引用可是实现缓存,弱引用可以用来在回调函数中防止内存泄漏。
四、数据库数据库索引,什么是全文索引,全文索引中的倒排索引是什么原理;
https://blog.csdn.net/u011066470/article/details/51768314
数据库最佳左前缀原则是什么?
https://blog.csdn.net/SkySuperWL/article/details/52583579
数据库的三大范式;
https://www.cnblogs.com/wujianrui/p/7077864.html
悲观锁和乐观锁的原理和应用场景;
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;
但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法.
什么是redo日志、什么是undo日志;
日志在内存里也是有缓存的,这里将其叫做log buffer。磁盘上的日志文件称为log file。log file一般是追加内容,可以认为是顺序写,顺序写的磁盘IO开销要小于随机写。
Undo日志记录某数据被修改前的值,可以用来在事务失败时进行rollback;Redo日志记录某数据块被修改后的值,可以用来恢复未写入data file的已成功事务更新的数据。下面的示例来自于杨传辉《大数据分布式存储系统 原理解析与架构实践》,略作改动。
例如某一事务的事务序号为T1,其对数据X进行修改,设X的原值是5,修改后的值为15,那么Undo日志为
,Redo日志为
。
也有把undo和redo结合起来的做法,叫做Undo/Redo日志,在这个例子中Undo/Redo日志为
。
当用户生成一个数据库事务时,undo log buffer会记录被修改的数据的原始值,redo会记录被修改的数据的更新后的值。
redo日志应首先持久化在磁盘上,然后事务的操作结果才写入db buffer,(此时,内存中的数据和data file对应的数据不同,我们认为内存中的数据是脏数据),db buffer再选择合适的时机将数据持久化到data file中。这种顺序可以保证在需要故障恢复时恢复最后的修改操作。先持久化日志的策略叫做Write Ahead Log
,即预写日志。
在很多系统中,undo日志并非存到日志文件中,而是存放在数据库内部的一个特殊段中。本文中就把这些存储行为都泛化为undo日志存储到undo log file中。
对于某事务T,在log file的记录中必须开始于事务开始标记(比如“start T”),结束于事务结束标记(比如“end T”、”commit T”)。在系统恢复时,如果在log file中某个事务没有事务结束标记,那么需要对这个事务进行undo操作,如果有事务结束标记,则redo。
在db buffer中的内容写入磁盘数据库文件之前,应当把log buffer的内容写入磁盘日志文件。
有一个问题,redo log buffer和undo log buffer存储的事务数量是多少,是按照什么规则将日志写入log file?如果存储的事务数量都是1个,也就意味着是将日志立即刷入磁盘,那么数据的一致性很好保证。在执行事T时,突然断电,如果未对磁盘上的redo log file发生追加操作,可以把这个事务T看做未成功。如果redo log file被修改,则认为事务是成功了,重启数据库使用redo log恢复数据到db buffer和 data file即可。
如果存储多个的话,其实也挺好解释的。就是db buffer写入data file之前,先把日志写入log file。这种方式可以减少磁盘IO,增加吞吐量。不过,这种方式适用于一致性要求不高的场合。因为如果出现断电等系统故障,log buffer、db buffer中的完成的事务会丢失。以转账为例,如果用户的转账事务在这种情况下丢失了,这意味着在系统恢复后用户需要重新转账。
checkpoint是为了定期将db buffer的内容刷新到data file。当遇到内存不足、db buffer已满等情况时,需要将db buffer中的内容/部分内容(特别是脏数据)转储到data file中。在转储时,会记录checkpoint发生的”时刻“。在故障回复时候,只需要redo/undo最近的一次checkpoint之后的操作。
在日志文件中的操作记录应该具有幂等性。幂等性,就是说同一个操作执行多次和执行一次,结果是一样的。例如,5*1 = 5*1*1*1
,所以对5的乘1操作具有幂等性。日志文件在故障恢复中,可能会回放多次(比如第一次回放到一半时系统断电了,不得不再重新回放),如果操作记录不满足幂等性,会造成数据错误。
数据库中的隔离性是怎样实现的;原子性、一致性、持久性又是如何实现的;
为了实现原子性,需要通过日志:将所有对数据的更新操作都写入日志,如果一个事务中的一部分操作已经成功,但以后的操作,由于断电/系统崩溃/其它的软硬件错误而无法继续,则通过回溯日志,将已经执行成功的操作撤销,从而达到“全部操作失败”的目的。最常见的场景是,数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程:读取日志进行REDO(重演将所有已经执行成功但尚未写入到磁盘的操作,保证持久性),再对所有到崩溃时尚未成功提交的事务进行UNDO(撤销所有执行了一部分但尚未提交的操作,保证原子性)。crash recovery结束后,数据库恢复到一致性状态,可以继续被使用。
为了保证并发情况下的一致性,引入了隔离性,即保证每一个事务能够看到的数据总是一致的,就好象其它并发事务并不存在一样。用术语来说,就是多个事务并发执行后的状态,和它们串行执行后的状态是等价的。怎样实现隔离性,已经有很多人回答过了,原则上无非是两种类型的锁:
一种是悲观锁,即当前事务将所有涉及操作的对象加锁,操作完成后释放给其它对象使用。为了尽可能提高性能,发明了各种粒度(数据库级/表级/行级……)/各种性质(共享锁/排他锁/共享意向锁/排他意向锁/共享排他意向锁……)的锁。为了解决死锁问题,又发明了两阶段锁协议/死锁检测等一系列的技术。
一种是乐观锁,即不同的事务可以同时看到同一对象(一般是数据行)的不同历史版本。如果有两个事务同时修改了同一数据行,那么在较晚的事务提交时进行冲突检测。实现也有两种,一种是通过日志UNDO的方式来获取数据行的历史版本,一种是简单地在内存中保存同一数据行的多个历史版本,通过时间戳来区分。
数据库死锁如何解决;
https://blog.csdn.net/qq_16681169/article/details/74784193
MySQL并发情况下怎么解决(通过事务、隔离级别、锁);
https://blog.csdn.net/zhangliangzi/article/details/51554204
MySQL中的MVCC机制是什么意思,根据具体场景,MVCC是否有问题;
https://blog.csdn.net/sofia1217/article/details/50778906
MySQL数据库的隔离级别,以及如何解决幻读;
未提交读(READUNCOMMITTED)。另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据(脏读)。
提交读(READCOMMITTED)。本事务读取到的是最新的数据(其他事务提交后的)。问题是,在同一个事务里,前后两次相同的SELECT会读到不同的结果(不重复读)。
可重复读(REPEATABLEREAD)。在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同样的SELECT操作读到的结果会是一致的。但是,会有幻读现象(稍后解释)。
串行化(SERIALIZABLE)。读操作会隐式获取共享锁,可以保证不同事务间的互斥。
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
Spring中@Autowired和@Resource注解的区别?
https://blog.csdn.net/wangzuojia001/article/details/54312074
Spring声明一个 bean 如何对其进行个性化定制;
https://blog.csdn.net/foreverling/article/details/50878599
MyBatis有什么优势;
https://blog.csdn.net/u014788653/article/details/68489301
MyBatis如何做事务管理;
https://blog.csdn.net/majinggogogo/article/details/72026693
Https和Http有什么区别;
https://www.cnblogs.com/wqhwe/p/5407468.html
Http 为什么是无状态的;
https://blog.csdn.net/wu1991924/article/details/8548051
TCP三次握手,为什么不是三次,为什么不是四次;
https://blog.csdn.net/to_be_better/article/details/54885684
TCP的拥塞控制、流量控制详细说明?
https://blog.csdn.net/qing101/article/details/48653797
Http1.0和Http2.0的区别;
https://blog.csdn.net/linsongbin1/article/details/54980801
如何防止表单重复提交(Token令牌环等方式);
https://blog.csdn.net/cor_twi/article/details/48596537
有三个线程T1 T2 T3,如何保证他们按顺序执行;
在后面的线程start()之前调用前面线程的join()方法,进行等待;
三个线程循环输出ABCABCABC....