1.Java面向对象解释一下
面向对象是向现实世界模型的自然延伸,这是一种万事万物皆对象的编程思想,在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的个例
2.Java面向对象的三大特征
继承 多态 封装
封装:是将一类事物的属性和行为抽象成一类,一般是使其属性私有化,行为公开化,提高了数据的隐秘性的同时,使代码模块化,好处是:将变化隔离,便于使用,提高代码复用性,提高安全性
继承:基于已有的类的定义为基础,构建新的类,已有的类称为父类,新构建的类称为子类,子类能调用父类的非private修饰的成员,同时还可以自己添加一些新的成员,扩充父类,甚至重写父类已有的方法,更其表现符合子类的特征
Java父类可以拥有多个子类,但是子类只继承一个父类,称为单继承
好处:实现了代码的复用,Java中所有的类都是通过直接或间接地继承JavaLangObject类得到的,子类不能继承父类中访问全年为private的成员变量和方法
,子类可以重写父类的方法,即命名与父类同名的成员变量
多态性:发送消息给某个对象,让该对象自行决定响应何种行为, 通过将子类德祥引用赋值给超类对象引用变量来实现动态方法调用
多态的两个实现:重载和重写
3.static的用法和静态内部类
静态变量;对于静态变量在内存中只有一个拷贝,JVM只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配,可以用类名直接访问,也可以通过对象来访问
需要实现以下两个功能使用静态变量:
在对象之间共享值,方便访问变量时
静态方法:可以直接通过类名调用,任何实例都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法。只能访问所属类的静态成员变量和成员方法,,因为实例成员与特定的对象关联。static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。一般类内部的static方法是为了方便其它类对该方法的调用,它们仅能调用其他的static 方法。静态方法是类内部的一类特殊方法,只有在需要时才将对应的方法声明成静态的,一个类内部的方法一般都是非静态的
静态代码块:是在勒种独立于类成员的static语句块,可以有多个,位置随便放,它不再任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM按照他们在类中的先后顺序执行,每个代码块只会被执行一次
静态内部类的限制以及与普通内部类的区别:
-
静态内部类不能访问外部类的非静态成员,它只能访问外部类的静态成员,非静态内部类能够访问外部类的静态和非静态成员。一般情况下,在非静态内部类中定义静态成员需同时有final关键词修饰,又鉴于静态方法无法用final修饰,故在非静态内部类中不定义静态成员变量与静态成员方法。但是在静态内部类中就可以定义静态的成员变量与成员方法。
-
静态内部类没有一个指向外部类的引用,而非静态内部类对象隐式地在外部类中保存了一个引用,指向创建它的外部类对象。一般的非静态内部类,可以随意的访问外部类中的成员变量与成员方法,即使这些成员变量或方法被修饰为private。但是静态内部类不能够从静态内部类的对象中访问外部类的非静态成员变量与成员方法。例如,在外部类中定义了两个变量,一个是非静态的变量,一个是静态的变量,那么在静态内部类中,无论在成员方法内部还是在其他地方,都只能够引用外部类中的静态的变量,而不能够访问非静态的变量。
-
静态内部类创建时不需要将静态内部类的实例绑定在外部类的实例上。通常情况下,在一个类中创建成员内部类的时候,有一个强制性的规定,即非静态内部类的实例一定要绑定在外部类的实例中。也就是说,在创建非静态内部类之前要先在外部类中要利用new关键字来创建这个内部类的对象。但在创建静态内部类的时候,不需要绑定在外部类的实例上,也就是说,要在一个外部类中定义一个静态的内部类,不需要利用关键字new来创建内部类的实例。
4.异常有哪些类
异常类分两大类型:Error类代表了编译和系统的错误,不允许捕获;Exception类代表了标准Java库方法所激发的异常。Exception类还包含运行异常类Runtime_Exception和非运行异常类Non_RuntimeException这两个直接的子类。
算术异常类:ArithmeticExecption
空指针异常类:NullPointerException
类型强制转换异常:ClassCastException
数组负下标异常:NegativeArrayException
数组下标越界异常:ArrayIndexOutOfBoundsException
违背安全原则异常:SecturityException
文件已结束异常:EOFException
文件未找到异常:FileNotFoundException
字符串转换为数字异常:NumberFormatException
操作数据库异常:SQLException
输入输出异常:IOException
5.final finally finalize的区别和用法
6.并发控制方法
为多线程、多任务间的协作和数据共享提供并发控制。常用方法:内部锁、重入锁、读写锁、信号量等。
一、volatile
由于每个线程有自己的私有内存空间(计数器、本地方法栈、虚拟机栈),同时还保存了主内存中的共享变量的值的拷贝。因此如果线程想改变自己工作内存的数据时,对其他线程是不可见的。为此可以使用volatile关键字迫使所有线程读写主内存中的对应变量,从而使得其在多线程间可见。
二、synchronized
采用同步方式
1.使用:
一种是锁定一个对象的方法,如 public synchronized void method(),当method方法调用时,调用线程首先获得当前对象的锁,若当前对象锁被其他线程持有,则调用线程会等待,方法结束后,释放对象锁,以上方法等价于
public void method(){ synchronized(this){ } }
一种是锁定代码块,可以精确的控制并发代码,
public void method(Object so ){
some code here ;
synchronized(so){
...
}
other code here ;
}
一种是加在static方法上,锁的是当前Class对象,因此所有对该方法的调用,都要获取该Class对象的锁。
以上实际上锁的是对象(所有同步代码段用的同一个对象独占锁),只能防止多个线程同时执行同一个对象的同步代代码码段,即使不同的同步代码段也是不能同时访问的。
2.notify和wait
通常情况下,虽然实现了同步效果,但是对于复杂的业务逻辑,常常配合Object对象的wait和notify方法。
wait过程中,释放对象锁,典型用法:
synchronized (obj )
{
while (condition)
obj.wait();
//收到通知后,继续执行
}
首先在wait前获得对象独占锁,循环进行状态判断,跳出后,wait方法执行后当前线程会释放对象锁,供其他线程使用
当等待在obj对象上的线程被notify后,会获得当前当前对象的独占锁,并继续运行。如果多个线程在等待,那么notify则随机选择一个
三、ReentrantLock重入锁
1.特性
(1).高并发下性能好,可中断、可定时
(2).公平锁和非公平锁:公平锁能保证线程间公平竞争锁对象,对锁的获取是有序的,非公平则无序,可能会出现插队现象
(3).必须手动调用unlock,否则会一直锁定程序,而synchronized随着jvm自动释放
2.重要方法
(1)lock:获得锁,如果锁被占用,就一直等待
(2)lockInerruptibly : 获得锁,但优先响应中断
(3)tryLock:尝试获得锁,成功返回true;失败返回false。该方法不等待,立即返回。
(4)tryLock(long timeout,TimeUnit unit),在给定时间内获得锁
(5)unlock:释放锁
注:lock方法会一直等待,直到获得锁,同时在等待锁的过程中,线程不会响应中断;而lockInterruptibly在线程等待锁过程中,可以优先响应中断。
总结:ReentrantLock重入锁提供了丰富的锁控制,如无等待的tryLock,还有优先响应中断的lockInterruptibly的锁。在锁竞争激烈的情况,这些灵活的锁可以提供更优的方案,从而提升系统性能
四、ReadWriteLock读写锁
jdk5提供的读写分离锁,可以有效减少锁竞争,提升系统性能。在读多的情况下,可以做到多个线程并行读操作,在写的时候还是使用锁。
适合于读多写少的场景
五、condition对象
六、Semaphone信号量
信号量为多线程协作提供了更强大的控制方法,是对锁的扩展。无论是的内部锁synchronized还是重入锁ReentrantLock都限制了只有一个线程访问一个资源,而信号量支持多个线程同时访问一个资源。
方法:
1.构造函数 :需要指定信号量的准入数,即多少个线程同时访问某个资源。
信号量对锁进行了扩展,可以限定对某个资源的最大可访问线程数。
七、ThreadLocal 线程局部变量
能保证不同线程中的局部变量独立性,但是不属于多线程的数据共享。
机制:不提供锁,而是使用以空间换时间的方式,为每个线程提供变量的独立副本,以保证线程安全,因此它不属于多线程间数据共享的方案
7.sycnorized ReentrantLock 的区别并分别解释
一、什么是sychronized
sychronized是java中最基本同步互斥的手段,可以修饰代码块,方法,类.
在修饰代码块的时候需要一个reference对象作为锁的对象.
在修饰方法的时候默认是当前对象作为锁的对象.
在修饰类时候默认是当前类的Class对象作为锁的对象.
synchronized会在进入同步块的前后分别形成monitorenter和monitorexit字节码指令.在执行monitorenter指令时会尝试获取对象的锁,如果此没对象没有被锁,或者此对象已经被当前线程锁住,那么锁的计数器加一,每当monitorexit被锁的对象的计数器减一.直到为0就释放该对象的锁.由此synchronized是可重入的,不会出现自己把自己锁死.
二、什么ReentrantLock
以对象的方式来操作对象锁.相对于sychronized需要在finally中去释放锁。
三、synchronized和ReentrantLock的区别
1. 等待可中断
在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待. tryLock(long timeout, TimeUnit unit)。
ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定。
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断。
如果使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情。
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c) tryLock (long timeout, TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly: 如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2.公平锁与非公平锁
按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁. new RenentrantLock(boolean fair)
3.绑定多个Condition
通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal()
此外,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中。在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
8.HashMap源码解释
9.HashTable和HashMap currentHashMap的区别
10.currentHashMap底层源码解释
11.线程池及参数有哪几种
先来看一下 ThreadPoolExecutor 的几个参数和它们的意义,先来看一下它最完整参数的重载。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
一共有 7 个参数。
corePoolSize
核心线程数,当有任务进来的时候,如果当前线程数还未达到 corePoolSize 个数,则创建核心线程,核心线程有几个特点:
1、当线程数未达到核心线程最大值的时候,新任务进来,即使有空闲线程,也不会复用,仍然新建核心线程;
2、核心线程一般不会被销毁,即使是空闲的状态,但是如果通过方法 allowCoreThreadTimeOut(boolean value) 设置为 true 时,超时也同样会被销毁;
3、生产环境首次初始化的时候,可以调用 prestartCoreThread() 方法来预先创建所有核心线程,避免第一次调用缓慢;
maximumPoolSize
除了有核心线程外,有些策略是当核心线程完全无空闲的时候,还会创建一些临时的线程来处理任务,maximumPoolSize 就是核心线程 + 临时线程的最大上限。临时线程有一个超时机制,超过了设置的空闲时间没有事儿干,就会被销毁。
keepAliveTime
这个就是上面两个参数里所提到的超时时间,也就是线程的最大空闲时间,默认用于非核心线程,通过 allowCoreThreadTimeOut(boolean value) 方法设置后,也会用于核心线程。
unit
这个参数配合上面的 keepAliveTime ,指定超时的时间单位,秒、分、时等。
workQueue
等待执行的任务队列,如果核心线程没有空闲的了,新来的任务就会被放到这个等待队列中。这个参数其实一定程度上决定了线程池的运行策略,为什么这么说呢,因为队列分为有界队列和无界队列。
有界队列:队列的长度有上限,当核心线程满载的时候,新任务进来进入队列,当达到上限,有没有核心线程去即时取走处理,这个时候,就会创建临时线程。(警惕临时线程无限增加的风险)
无界队列:队列没有上限的,当没有核心线程空闲的时候,新来的任务可以无止境的向队列中添加,而永远也不会创建临时线程。(警惕任务队列无限堆积的风险)
threadFactory
它是一个接口,用于实现生成线程的方式、定义线程名格式、是否后台执行等等,可以用 Executors.defaultThreadFactory() 默认的实现即可,也可以用 Guava 等三方库提供的方法实现,如果有特殊要求的话可以自己定义。它最重要的地方应该就是定义线程名称的格式,便于排查问题了吧。
handler
当没有空闲的线程处理任务,并且等待队列已满(当然这只对有界队列有效),再有新任务进来的话,就要做一些取舍了,而这个参数就是指定取舍策略的,有下面四种策略可以选择:
ThreadPoolExecutor.AbortPolicy:直接抛出异常,这是默认策略;
ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后将新来的任务加入等待队列
ThreadPoolExecutor.CallerRunsPolicy:由线程池所在的线程处理该任务,比如在 main 函数中创建线程池,如果执行此策略,将有 main 线程来执行该任务
1、newFixedThreadPool
它有两个重载方法,代码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
建立一个线程数量固定的线程池,规定的最大线程数量,超过这个数量之后进来的任务,会放到等待队列中,如果有空闲线程,则在等待队列中获取,遵循先进先出原则。
创建固定线程数量线程池, corePoolSize 和 maximumPoolSize 要一致,即核心线程数和最大线程数(核心+非核心线程)一致,Executors 默认使用的是 LinkedBlockingQueue 作为等待队列,这是一个无界队列,这也是使用它的风险所在,除非你能保证提交的任务不会无节制的增长,否则不要使用无界队列,这样有可能造成等待队列无限增加,造成 OOM。
2、newSingleThreadExecutor
建立一个只有一个线程的线程池,如果有超过一个任务进来,只有一个可以执行,其余的都会放到等待队列中,如果有空闲线程,则在等待队列中获取,遵循先进先出原则。使用 LinkedBlockingQueue 作为等待队列。
这个方法同样存在等待队列无限长的问题,容易造成 OOM,所以正确的创建方式参考上面固定数量线程池创建的方式,只是把 poolSize 设置为 1 。
3、newCachedThreadPool
缓存型线程池,在核心线程达到最大值之前,有任务进来就会创建新的核心线程,并加入核心线程池,即时有空闲线程,也不会复用
达到最大核心进程数后,新任务进来,如果有空闲线程,则直接拿来使用,如果没有空闲线程,则新建临时线程,并且线程的允许空闲时间都很短,如果超过空闲
时间没有活动,则销毁临时线程。关键点就在于它使用SynchronousQueue作为等待队列,它不会保留任务,新任务进来后,直接创建临时线程处理,这样一来
也就容易造成无限制的创建线程,造成 OOM。
4、newScheduledThreadPool
计划型线程池,可以设置固定时间的延时或者定期执行任务,同样是看线程池中有没有空闲线程,如果有,直接拿来使用,如果没有,则新建线程加入池。使用的是 DelayedWorkQueue 作为等待队列,这中类型的队列会保证只有到了指定的延时时间,才会执行任务。
12.ThreadLocal解释
13.Mysql存储引擎哪几种,区别哪几种
(一)MyISAM
它不支持事务,也不支持外键,尤其是访问速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用基本都可以使用这个引擎来创建表。
每个MyISAM在磁盘上存储成3个文件,其中文件名和表名都相同,但是扩展名分别为:
- .frm(存储表定义)
- MYD(MYData,存储数据)
- MYI(MYIndex,存储索引)
数据文件和索引文件可以放置在不同的目录,平均分配IO,获取更快的速度。要指定数据文件和索引文件的路径,需要在创建表的时候通过DATA DIRECTORY和INDEX DIRECTORY语句指定,文件路径需要使用绝对路径。
每个MyISAM表都有一个标志,服务器或myisamchk程序在检查MyISAM数据表时会对这个标志进行设置。MyISAM表还有一个标志用来表明该数据表在上次使用后是不是被正常的关闭了。如果服务器以为当机或崩溃,这个标志可以用来判断数据表是否需要检查和修复。如果想让这种检查自动进行,可以在启动服务器时使用--myisam-recover现象。这会让服务器在每次打开一个MyISAM数据表是自动检查数据表的标志并进行必要的修复处理。MyISAM类型的表可能会损坏,可以使用CHECK
TABLE语句来检查MyISAM表的健康,并用REPAIR TABLE语句修复一个损坏到MyISAM表。
MyISAM的表还支持3种不同的存储格式:
- 静态(固定长度)表
- 动态表
- 压缩表
其中静态表是默认的存储格式。静态表中的字段都是非变长字段,这样每个记录都是固定长度的,这种存储方式的优点是存储非常迅速,容易缓存,出现故障容易恢复;缺点是占用的空间通常比动态表多。静态表在数据存储时会根据列定义的宽度定义补足空格,但是在访问的时候并不会得到这些空格,这些空格在返回给应用之前已经去掉。同时需要注意:在某些情况下可能需要返回字段后的空格,而使用这种格式时后面到空格会被自动处理掉。
动态表包含变长字段,记录不是固定长度的,这样存储的优点是占用空间较少,但是频繁到更新删除记录会产生碎片,需要定期执行OPTIMIZE TABLE语句或myisamchk -r命令来改善性能,并且出现故障的时候恢复相对比较困难。
压缩表由myisamchk工具创建,占据非常小的空间,因为每条记录都是被单独压缩的,所以只有非常小的访问开支。
(二)InnoDB
InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。
1)自动增长列:
InnoDB表的自动增长列可以手工插入,但是插入的如果是空或0,则实际插入到则是自动增长后到值。可以通过"ALTER
TABLE...AUTO_INCREMENT=n;"语句强制设置自动增长值的起始值,默认为1,但是该强制到默认值是保存在内存中,数据库重启后该值将会丢失。可以使用LAST_INSERT_ID()查询当前线程最后插入记录使用的值。如果一次插入多条记录,那么返回的是第一条记录使用的自动增长值。
对于InnoDB表,自动增长列必须是索引。如果是组合索引,也必须是组合索引的第一列,但是对于MyISAM表,自动增长列可以是组合索引的其他列,这样插入记录后,自动增长列是按照组合索引到前面几列排序后递增的。
2)外键约束:
MySQL支持外键的存储引擎只有InnoDB,在创建外键的时候,父表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引。
在创建索引的时候,可以指定在删除、更新父表时,对子表进行的相应操作,包括restrict、cascade、set null和no
action。其中restrict和no
action相同,是指限制在子表有关联的情况下,父表不能更新;casecade表示父表在更新或删除时,更新或者删除子表对应的记录;set
null 则表示父表在更新或者删除的时候,子表对应的字段被set null。
当某个表被其它表创建了外键参照,那么该表对应的索引或主键被禁止删除。
可以使用set foreign_key_checks=0;临时关闭外键约束,set foreign_key_checks=1;打开约束。
(三)MEMORY
memory使用存在内存中的内容来创建表。每个MEMORY表实际对应一个磁盘文件,格式是.frm。MEMORY类型的表访问非常快,因为它到数据是放在内存中的,并且默认使用HASH索引,但是一旦服务器关闭,表中的数据就会丢失,但表还会继续存在。
默认情况下,memory数据表使用散列索引,利用这种索引进行“相等比较”非常快,但是对“范围比较”的速度就慢多了。因此,散列索引值适合使用在"="和"<=>"的操作符中,不适合使用在"<"或">"操作符中,也同样不适合用在order
by字句里。如果确实要使用"<"或">"或betwen操作符,可以使用btree索引来加快速度。
存储在MEMORY数据表里的数据行使用的是长度不变的格式,因此加快处理速度,这意味着不能使用BLOB和TEXT这样的长度可变的数据类型。VARCHAR是一种长度可变的类型,但因为它在MySQL内部当作长度固定不变的CHAR类型,所以可以使用。
create table tab_memory engine=memory select id,name,age,addr from man order by id; |
使用USING HASH/BTREE来指定特定到索引。
create index mem_hash using hash on tab_memory(city_id); |
在启动MySQL服务的时候使用--init-file选项,把insert into...select或load data infile 这样的语句放入到这个文件中,就可以在服务启动时从持久稳固的数据源中装载表。
服务器需要足够的内存来维持所在的在同一时间使用的MEMORY表,当不再使用MEMORY表时,要释放MEMORY表所占用的内存,应该执行DELETE FROM或truncate table或者删除整个表。
每个MEMORY表中放置到数据量的大小,受到max_heap_table_size系统变量的约束,这个系统变量的初始值是16M,同时在创建MEMORY表时可以使用MAX_ROWS子句来指定表中的最大行数。
(四)MERGE
merge存储引擎是一组MyISAM表的组合,这些MyISAM表结构必须完全相同,MERGE表中并没有数据,对MERGE类型的表可以进行查询、更新、删除的操作,这些操作实际上是对内部的MyISAM表进行操作。对于对MERGE表进行的插入操作,是根据INSERT_METHOD子句定义的插入的表,可以有3个不同的值,first和last值使得插入操作被相应的作用在第一个或最后一个表上,不定义这个子句或者为NO,表示不能对这个MERGE表进行插入操作。可以对MERGE表进行drop操作,这个操作只是删除MERGE表的定义,对内部的表没有任何影响。MERGE在磁盘上保留2个以MERGE表名开头文件:.frm文件存储表的定义;.MRG文件包含组合表的信息,包括MERGE表由哪些表组成,插入数据时的依据。可以通过修改.MRG文件来修改MERGE表,但是修改后要通过flush
table刷新。
create
table
man_all(id
int
,
name
varchar
(20))engine=merge
union
=(man1,man2) insert_methos=
last
;
14.事务的特性,隔离级别
事务的特性ACID:
1)原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2)一致性(Consistency)一个事务中,事务前后数据的完整性必须保持一致。
3)隔离性(Isolation)多个事务,事务的隔离性是指多个用户并发访问数据库时, 一个用户的 事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
4)持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变 就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
事务的隔离级别:
1)read uncommitted : 读取尚未提交的数据 :哪个问题都不能解决
2)read committed:读取已经提交的数据 :可以解决脏读 ---- oracle、sql server、postgresql 默认的
3)repeatable read:重读读取:可以解决脏读 和 不可重复读 ---mysql默认的
4)serializable:串行化:可以解决 脏读 不可重复读 和 虚读---相当于锁表
15.Redis基本数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)
16.Redis持久化
简单聊聊Redis的两种持久化机制RDB和AOF:
一、RDB
1、RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。RDB是Redis默认的持久化方式,会在对应的目录下生产一个dump.rdb文件,重启会通过加载dump.rdb文件恢复数据。
2、优点
1)只有一个文件dump.rdb,方便持久化;
2) 容灾性好,一个文件可以保存到安全的磁盘;
3) 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化(使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能) ;
4)如果数据集偏大,RDB的启动效率会比AOF更高。
3、缺点
1)数据安全性低。(RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不是特别严格的时候)
2)由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
二、AOF
1、AOF持久化是以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,文件中可以看到详细的操作记录。她的出现是为了弥补RDB的不足(数据的不一致性),所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启的会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
2、优点
1)数据安全性更高,AOF持久化可以配置appendfsync属性,其中always,每进行一次命令操作就记录到AOF文件中一次。
2)通过append模式写文件,即使中途服务器宕机,可以通过redis-check-aof工具解决数据一致性问题。
3)AOF机制的rewrite模式。(AOF文件没被rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的flushall))
3、缺点
1)AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比rdb启动效率低。
2)根据同步策略的不同,AOF在运行效率上往往会慢于RDB。
17.缓存穿刺和缓存雪崩处理方式
一.缓存穿透:
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决办法:
1.布隆过滤
对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
补充:
Bloom filter
适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集
基本原理及要点:对于原理来说很简单,位数组+k个独立hash函数。将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查找的结果是100%正确的。同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字。所以一个简单的改进就是counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了。添加时增加计数器,删除时减少计数器。
2. 缓存空对象. 将 null 变成一个值.
也可以采用一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存空对象会有两个问题:
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
二.缓存雪崩
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
解决方法
1. 加锁排队. 限流-- 限流算法. 1.计数 2.滑动窗口 3. 令牌桶Token Bucket 4.漏桶 leaky bucket [1]
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
2.数据预热
可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
3.做二级缓存,或者双缓存策略。
A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
4.缓存永远不过期
这里的“永远不过期”包含两层意思:
(1) 从缓存上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期.
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。
18.kafia组件
19.为什么用spark,spark运行机制讲一下
20.Spark和Hadoop的区别,有什么优势