----------------------java
一。创建一个线程需要设置哪些属性
1.设置线程id
2.设置线程名称
3.设置是否守护线程(简单来说就是用户线程的保姆,当所有的用户线程结束工作时,守护线程就会结束工作,jvm退出工作,守护线程对经典的应用就是GC)
4.设置优先级(setPriority)
sleep wait
1.sleep 不会释放锁,sleep是thred方法
2.wait会释放锁,wait是object方法
synchronized 和 thredlocal 都可以保证共享变量安全
二。线程池有哪些参数及原理
作用:1.重用线程池的线程,避免因为线程的创建和销毁锁带来的性能开销
2.有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞
3.能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线程、并发数控制等功能
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
ThreadPoolExecutor执行execute方法有4种情况:
1)如果当前运行的线程少于corePoolSize,则创建新的线程来执行任务。
2)如果运行的线程等于或者多余corePoolSize,则将任务加入BlockingQueue中,在等待队列中,等待有新的线程可以运行。
3)如果BlockingQueue队列满了,且没有超过maxPoolSize,则创建新的线程来处理任务。
4)如果创建的线程超过maxPoolSize,任务会拒绝,并调用RejectExecutionHandler.rejectedExecution()方法。
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,
直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,
直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,
keepAliveTime参数也会起作用,直到线程池中的线程数为0;
4) workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue:一个基于数组结构的有界阻塞队列。
LinkedBlockingQueue:一个基于链表的阻塞队列,吞吐量要高于ArrayBlockingQueue。
SynchronousQueue:一个不存储元素的阻塞队列。每次插入操作必须等到另外一个线程调用移除操作,否则一直处于阻塞状态。吞吐量要高于LinkedBlockingQueue。
PriorityBlockingQueue:一个具有优先级的无线阻塞队列。
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
5)threadFactory:线程工厂,主要用来创建线程;
6)RejectedExecutionHandler:当达到最大线程数,将会执行下面的策略,jdk1.5中提供有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
向线程池提交任务,提供了两种方法,分别是execute()和submit()方法。
execute方法用于提交不需要返回值的任务,所以也就意味着无法判断是否执行成功。
submit方法可以用于提交需要有返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判读是否执行成功,并且还可以通过get()方法来获取返回值。
如何合理设置线程池大小:
对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,
IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,
而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。
若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。
当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。
在这篇如何合理地估算线程池大小?文章中发现了一个估算合理值的公式
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
最佳线程数目 = (线程总时间与线程CPU时间之比 + 1)* CPU数目
IO密集型任务 :一般来说:文件读写、DB读写、网络请求等
CPU密集型任务 :一般来说:计算型代码、Bitmap转换、Gson转换等
三。线程安全,及锁机制,什么是死锁
线程安全:当多个线程访问同一个对象,并调用这个对象的行为,可以得到正确的结果,那么,这个对象是线程安全的
锁机制:目前的锁 就synchonized(悲观锁) 和 lock(乐观锁)
独享锁/共享锁: 读锁 是共享锁,写锁是 独享锁
可重入锁: Synchronized 和 ReentrantLock 都是 可重入锁
公平锁/非公平锁:ReetrantLock而言,通过构造函数指定该锁是否是公平锁,默认是false非公平锁,true时为公平锁。非公平锁的优点在于吞吐量比公平锁大,
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
分段锁: 分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7和JDK8中HashMap的实现)的结构,
即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,
只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
偏向锁/轻量级锁/重量级锁: 这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。
这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,
该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。
自旋锁: 在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
什么是死锁:当两个线程相互等待对方释放资源时,就会发生死锁。例:线程1持有A锁,此时需要B锁,线程2持有B锁,此时需要A锁,相互等待
避免死锁:
减少同步代码块嵌套操作
降低锁的使用粒度,不要几个功能共用一把锁
四。synchronized是如何实现的,和lock的区别
区别如下:
来源:
lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;
异常是否释放锁:
synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)
是否响应中断
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;
是否知道获取锁
Lock可以通过trylock来知道有没有获取锁,而synchronized不能;
Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,
Synchronized在很多情况下不是最优的选择,
Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。
尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。
自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
五。CAS原理
compare and swap 比较并替换
变量A = 10
线程1 对应变量A 内存地址存的值是 10,旧的预期值是10, 新的预期值是 11,此时 线程1要去对A执行 +1 的操作
线程2 抢先一步 对变量A执行了 +1操作,此时
线程1 内存地址存的值为11,和旧的预期值10比较,不相等,提交失败,
线程1重新获取内存地址V的当前值,更新旧的预期值为11,新的预期值为12,再次去比较。这个重新尝试的过程被称为自旋。
这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,11=11是相等的。
线程1进行SWAP,把地址V的值替换为新的预期值,也就是12。
六。volatile 关键字作用
1. 可见性 :如果一个线程对共享变量值的修改,能够及时的被其他线程看到,叫做共享变量的可见性。
如果一个变量同时在多个线程的工作内存中存在副本,那么这个变量就叫共享变量
两条规定:
a.线程对共享变量的所有操作必须在工作内存中进行,不能直接操作主内存
b.不同线程间不能访问彼此的工作内存中的变量,线程间变量值的传递都必须经过主内存
如果一个线程1对共享变量x的修改对线程2可见的话,需要经过下列步骤:
a.线程1将更改x后的值更新到主内存
b.主内存将更新后的x的值更新到线程2的工作内存中x的副本
所以,要实现共享变量的可见性必须保证下列两点:
a.线程对工作内存中副本的更改能够及时的更新到主内存上
b.其他线程能够及时的将主内存上共享变量的更新刷新到自己工作内存的该变量的副本上
Java中可以通过synchronized、volatile、java concurrent类来实现共享变量的可见性
int i = 0;
boolean flag = false;
i = 1; //语句1
flag = true; //语句2
指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,
它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。
因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。
虽然重排序不会影响单个线程内程序执行的结果,但是多线程呢?
//线程1:
context = loadContext(); //语句1
inited = true; //语句2
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
线程1 先执行语句2,线程2执行到 while循环里面的时候,其实还没初始化完成,发现inited = true; 跳出循环,导致初始化还没完就去doSomethingwithconfig,可能会报错
指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
volatile变量每次被线程访问时,都强迫线程从主内存中重读该变量的最新值,而当该变量发生修改变化时,也会强迫线程将最新的值刷新回主内存中。
这样一来,不同的线程都能及时的看到该变量的最新值。
通过 内存屏障 做到 防止 指令重排序
内存屏障也称为内存栅栏或栅栏指令,是一种屏障指令,它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束。
这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。
LoadLoad屏障:
抽象场景:Load1; LoadLoad; Load2
Load1 和 Load2 代表两条读取指令。在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:
抽象场景:Store1; StoreStore; Store2
Store1 和 Store2代表两条写入指令。在Store2写入执行前,保证Store1的写入操作对其它处理器可见
LoadStore屏障:
抽象场景:Load1; LoadStore; Store2
在Store2被写入前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:
抽象场景:Store1; StoreLoad; Load2
在Load2读取操作执行前,保证Store1的写入对所有处理器可见。StoreLoad屏障的开销是四种屏障中最大的。
在一个变量被volatile修饰后,JVM会为我们做两件事:
1.在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。
2.在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。
volatile特性之一:
保证变量在线程之间的可见性。可见性的保证是基于CPU的内存屏障指令,被JSR-133抽象为happens-before原则。
volatile特性之二:
阻止编译时和运行时的指令重排。编译时JVM编译器遵循内存屏障的约束,运行时依靠CPU屏障指令来阻止重排。
七。使用了哪些设计模式,为什么用单例,观察者模式,工厂设计模式? 向项目中是否应用到,spring中用到了哪些设计模式?如何应用
1.单例模式,手写
//双层检测
public class SignleTon{
private SignleTon(){};//构造函数私有化
private static volatile SignleTon instance = null;
public static SignleTon getSignleTon(){
if (instance == null){
synchronized(SignleTon.class){
if (instance == null){
instance = new SignleTon();
}
}
}
return instance;
}
}
//用静态内部类实现单例模式:
public class SignleTon{
private Singleton (){};
private static class LazyHold{
private static final SignleTon INSTANCE = = new SignleTon();
}
public static SignleTon getSignleTon(){
return LazyHold.INSTANCE;
}
}
为什么用单例:在内存中只有一个实例对象,节省内存空间.避免重复的创建和销毁对象,可以提高性能,避免对多重资源的重复占用,可以全局进行访问.
单例模式的使用场景:单例的使用主要是在需要保证全局只有一个实例可以被访问的情况,比如系统日志的输出、操作系统的任务管理器等。
2.观察者模式
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
在观察者设计模式中,一般有四个角色:
抽象主题角色(Subject)
具体主题角色(ConcreteSubject)
抽象观察者角色(Observer)
具体观察者角色(ConcreteObserver)
其中,【主题】需要有一个列表字段,用来保存【观察者】的引用,提供两个方法(虚方法),即【删除观察者】【增加观察者】,还需要提供一个给客户端调用的方法,
通知各个【观察者】:你们关心(订阅)的事件已经推送给你们了。
八。类的加载流程
加载 - 验证 - 准备 - 解析 - 初始化 - 使用 - 卸载
加载:通过类名 获取类的 二进制字节流 是通过类加载器完成的。其加载过程使用 “双亲委派模型”
验证:判断类是否符合java规范
准备:对类的静态变量进行分配内存
解析:把常量池中的符号引用解析为直接引用:根据符号引用所作的描述,在内存中找到符合描述的目标并把目标指针指针返回。
使用:使用类
卸载:将类销毁
九。什么是双亲委派
类加载器分为4类:
启动类加载器(Bootstrap ClassLoader):主要加载的是 <JAVA_HOME>/lib 下面的核心类库
↓↓
扩展类加载器(Extension ClassLoader):主要加载的是 <JAVA_HOME>/lib/ext 下面的扩展类库
↓↓
系统类加载器(Application ClassLoader): 主要加载 java.class.path 指定路径下的类库
↓↓
自定义类加载器:
工作原理:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,
如果父类加载器可以完成类加载任务,就成功返回,
倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
双亲委派模型优势:
1.防止类被重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
2.防止核心API库被随意篡改。如:假设通过网络传递一个名为java.lang.Integer的类
通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载
并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
十。IO模型,什么是nio?
比如程序从磁盘读取一个文件
内核 -》 磁盘
1.同步阻塞IO
2.同步非阻塞IO
3.IO多路复用 NIO
4.信号驱动
5.异步IO
十一。聊聊TCP
TCP处于传输层,一个正常的TCP连接从建立到分开,要完成三次握手四次挥手.
三次握手:客户端向服务端发送请求,进入等待状态,
-------------数据结构
1.什么是二叉树,红黑树
2.实现排列组合
3.什么是B+树
4.冒泡排序
5.快速排序
6.希尔排序
7.二分查找
8.并查集
9.跳跃表
10.动态规划
-------------Spring
一。spring IOC DI
控制反转(Inversion of Control) 就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的依赖注入(Dependency Injection)
所以我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。
所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。这里我们用构造方法传递的依赖注入方式重新写车类的
二。什么是循环依赖,如何解决
Class A{
private B b;
}
Class B {
private A a;
}
一级缓存 Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
二级缓存 Map<String, Object> earlySingletonObjects = new HashMap(16)
三级缓存 Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
1.实例化A,先从 一级缓存里面获取A对象,发现没有,从二级缓存获取,没有,从三级缓存里获取,没有则createBean A,将A放入三级缓存。初始化属性 B。
2.实例化B,先从 一级缓存里面获取B对象,发现没有,从二级缓存获取,没有,从三级缓存里获取,没有则createBean B,将B放入三级缓存。初始化属性 A。
3.此时 B实例化A的时候,发现,A在三级缓存里面,直接获取A,B实例化完成,出栈,返回到实例化 A的方法栈中。
4 此时A 的B属性赋值成功。
5.将A放入一级缓存
类的初始化: 是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。
同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次
类的实例化(实例化对象):是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。
在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。(就是调用构造函数)
三。springCloud 熔断
四。springBoot 启动原理及过程
五。oject类有哪些方法
九。对称加密和飞对称加密的区别
十.linux 执行shell 有哪些返回码
十一。JVM 内存模型
1.程序计数器:记录程序指令码的行数 (线程私有)
2.本地方法栈:执行java本地方法(线程私有)
3.虚拟机栈 :每执行一个方法,都会开辟一个栈帧,栈帧由 局部变量表,操作数栈,动态链接,方法出口组成
4.heap堆 :创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 垃圾收集的最重要的内存区域
5.元空间 :用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据.
JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
JDK1.8开始,取消了Java方法区,取而代之的是位于直接内存的元空间(metaSpace)。
JVM 的主要任务是负责装载字节码到其内部,但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令,
它内部包含的仅仅只是一些能够被 JVM 所识别的字节码指令、符号表,以及其他的辅助信息。
那么,如果想让一个 java 程序运行起来,执行引擎(Execution Engine) 的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。
十二。如何用http实现https,SSL之类
十二。
------------数据库
一。数据库设计三大范式
主码primary key :能够唯一标识一个实体。
第一范式条件:必须不包含重复组的关系,即每一列都是不可拆分的原子项。
第二范式条件:关系模式必须满足第一范式,并且所有非主属性都完全依赖于主码
举例如关系模型(职工号,姓名,职称,项目号,项目名称)中,职工号->姓名,职工号->职称,而项目号->项目名称。
显然依赖关系不满足第二范式,常用的解决办法是差分表格,比如拆分为职工信息表和项目信息表。
第三范式的条件:关系模型满足第二范式,所有非主属性对任何候选关键字都不存在传递依赖。即每个属性都跟主键有直接关系而不是间接关系,像:a-->b-->c。一般数据库设计中,一般要求达到3NF,第四第五较少涉及。
比如Student表(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)这样一个表结构,就存在上述关系。 学号--> 所在院校 --> (院校地址,院校电话)。我们应该拆开来,如下:
(学号,姓名,年龄,性别,所在院校)--(所在院校,院校地址,院校电话)
二。数据库的事务性
事务处理的特性,每一个事务都有他们所共有的特性,叫做ACID特性,分别是原子性atomicity,一致性consistency、隔离性Isolation,持久性Durability。
原子性,事务的原子性表示事务执行过程中,把事务作为一个工作单元处理,一个工作单元可能包括若干个操作步骤,每个操作步骤都必须完成才算完成,若因任何原因导致其中的一个步骤操作失败,则所有步骤操作失败,前面的步骤必须回滚。
一致性,事务的一致性保证数据处于一致状态。如果事务开始时系统处于一致状态,则事务结束时系统也应处于一致状态,不管事务成功还是失败。
隔离性,事务的隔离性保证事务访问的任何数据不会受到其他事务所做的任何改变的影响,直到该事务完成。
持久性,事务的持久性保证加入事务执行成功,则它在系统中产生的结果应该是持久的。
三。MySQL、Oracle、SqlServer 的默认事务隔离级别
在Oracle,SqlServer中事务隔离级别都是 读已提交(Read Commited)
mysql默认的事务处理级别是'REPEATABLE-READ',也就是可重复读
为什么Mysql默认不选择读已提交而选择可重复读?
串行化(Serializable),每个次读操作都会加锁
四。在互联网项目为什么将隔离级别设为读已提交(Read Commited)!
可重复读(Repeatable Read),简称为RR;
读已提交(Read Commited),简称为RC;
select * from test where id <3 for update;
1.在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!
在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据!
而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据!
2.在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
五。对于脏读,不可重复读,幻读的一点理解。
脏读:主要针对 “读未提交”,事务1 读取一条数据,此时事务2 正在修改这条数据,然后回滚了,事务A读取的时错误的数据
不可重复读--针对修改:事务1 读取一条数据,此时事务2 正在修改这条数据,并提交,事务1在事务2提交前后 读取的数据不一致,
其实,这个是正常的,这个取决于你想要的数据库是什么样的。
不可重复读的重点是修改: 同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
幻读--针对增删:事务1 读取一条数据,此时事务2 新增一条数据,并提交,(如 查询工资大于2W的,事务2就新增一条符合条件的记录),事务1 在读取数据发现数据条数不一样。
幻读的重点在于新增或者删除 (数据条数变化) 同样的条件, 第1次和第2次读出来的记录数不一样
-------------中间件
一.redis 为什么采用单线程
redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,
就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。
二。rabbitMQ
--答案持续更新