面试要点准备
自我介绍
自我介绍提前准备好
问面试官的问题
- 对新员工的培训机制
- 部门业务现状和未来规划
- 技术栈
- 您对于职场新员工的期待/建议是什么
Java
1. 接口和抽象类的区别是什么?
- 抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用
- 接口可以 extends 多个接口
- 类可以实现很多个接口,但是只能继承一个抽象类
- 抽象类中可以有一个或多个抽象方法,而接口中的方法必须都是抽象方法
- 接口中的方法定义默认为
public abstract
类型,接口中的成员变量类型默认为public static final
- 抽象类和方法必须使用
abstract
关键声明为抽象,而接口中的方法默认被修饰为public abstract
类型(默认为公开抽象的方法)
2. Java8定义接口关键字
public、abstruct、static、default
public interface Test {
public void fun1();
abstruct void fun2();
static void fun3() {
System.out.println("hh");
}
default void fun4() {
System.out.println("hh");
}
}
3. 什么是值传递和引用传递
值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值
引用传递,意味着传递的并不是实际的对象,而是对象的引用,传递的是地址。因此,外部对引用对象所做的改变会反映到所有的对象上
4. HashMap和Hashtable有什么区别?
- HashMap允许键和值是null,而Hashtable不允许键或者值是null
- Hashtable是同步的,而HashMap不是。因此,HashMap更适合於单线程环境,而Hashtable适合于多线程环境
- HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举
5. 说出ArrayList,Vector, LinkedList的存储性能和特性
-
ArrayList:
- 底层数据结构是数组,查询快,增删慢
- 线程不安全,效率高
- 按下标访问的o(1)时间复杂度,访问速度读快,但增删数据需要移动后面的所有数据,需要o(n)的时间复杂度
- Arraylist默认大小的10,每次扩容增加1.5倍
-
Vector:
- 底层数据结构是数组,查询快,增删慢
- 线程安全,效率低
-
LinkedList:
- 底层数据结构是链表,查询慢,增删快,由一个个Node节点构成,继承了deque双端队列
- 链表结构特性就是在两端增删节点速度快,但按照索引访问效率低,是O(N/2),头尾就近原则
- 按需分配空间,不会浪费空间
- 线程不安全,效率高
6. List、set有序无序
- Set
HashSet无序,LinkedHashSet(插入顺序)和TreeSet(字母排序/字典序)有序 - List
List集合是有序的
7. HashSet和TreeSet
- HashSet的底层是HashMap
- TreeSet的底层是TreeMap
8. TreeSet使用比较器
在Comparable和Compartor排序比较器都存在的情况下,Compartor外部比较器优先,因为在TreeMap获取元素getEntry方法中,优先判断Compartor比较器,存在则返回元素
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
9. Comparable和Compartor
- Comparable
内部比较器,在需要比较的对象中实现Comparable接口,并且重写compareTo方法public class Girl implements Comparable<Object> { private int age; ... @Override public int compareTo(Object o) { Girl g = (Girl)o; return this.age - g.getAge(); } }
- Compartor
外部比较器,新建一个比较器类,实现Compartor接口,重写compare方法public class GirlComparator implements Comparator<Girl> { @Override public int compare(Girl g1, Girl g2) { return g1.getAge() - g2.getAge(); } }
10. Collection 和 Collections的区别
- Collection是集合类的上级接口,继承与他的接口主要有Set 和List
- Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作
11. &和&&的区别
&
是位运算符,表示按位与运算,&&
是逻辑运算符,表示逻辑与(and)
12. final和finally、finalize的区别
- final:关键字,修饰类,不能派生子类,修饰变量或方法,不能被改变
- finally:异常处理语句块,如果抛出一个异常,那么相匹配的catch语句就会执行,然后控制就会进入finally块
- finalize:方法名,是在 Object 类中定义的,因此所有的类都继承了它,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等
13. try和finally中同时return会返回什么
返回finally语句块内容
14. equals ==区别
如果没有重写,其实是一样的,都是比较地址。可以重写成比较值
15. sleep() 和 wait() 有什么区别?
Thread.sleep()
只会让出CPU,不会释放对象锁,不释放所占有的资源。Object.wait()
不仅会让出CPU,对象调用wait方法还会导致本线程放弃对象锁,释放所占有的所有资源,进入等待此对象的等待锁定池,不能自动唤醒,只有针对此对象发出notify()
方法(或notifyAll()
)后本线程才进入对象锁定池准备获得对象锁进入运行状态
16. Java中异常分类
- 运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。 - 非运行时异常(编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
17. error和exception有什么区别?
- Error(错误)是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序才能修正。一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止
- Exception(异常)表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常
18. Java中的异常处理机制的简单原理和应用
- 当JAVA 程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。
- 所有的异常都是java.lang.Thowable的子类
19. 同步有几种实现方法,都是什么?
- 同步的实现方面有两种,分别是同步代码块和同步方法
20. synchronized和ReentrantLock的区别
- synchronized:关键字,Java语言内置,不需要手动释放锁,在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
- ReentrantLock:类,通过这个类可以实现同步访问,需要用户手动释放锁,并且必须在finally从句中释放,否则可能导致死锁,Lock可以让等待锁的线程响应中断,可以提高多个线程进行读操作的效率
21. 重载(Overload)和重写(Override)的区别?
- 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同甚至是参数顺序)则视为重载;
- 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
22. throw和throws
- throw语句用来明确地抛出一个”异常”,写在代码块中。
- throws用来标明一个成员函数可能抛出的各种”异常”,写在方法后
23. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
- 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。
- 当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。可以。程序员可以手动执行System.gc(),通知GC运行,但是Java虚拟机并不保证GC一定会执行
24. 什么是java序列化,如何实现java序列化?
- 序列化就是一种用来处理对象流的机制,把Java对象转换为二进制的数据流。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
- 序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流
25. 是否可以从一个static方法内部发出对非static方法的调用?
- 不可以,如果其中包含对象的method();不能保证对象初始化
26. 使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
- 使用final关键字修饰一个变量时,是指引用地址不能变,引用变量所指向的对象中的内容还是可以改变的
final StringBuffer a=new StringBuffer(“immutable”); 执行如下语句将报告编译期错误: a=new StringBuffer(“”); 但是,执行如下语句则可以通过编译: a.append(” broken!”);
27. 请说出作用域public,private,protected,以及不写时的区别
作用域 | 当前类 | 同一package | 子类 | 其它package |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
friendly | √ | √ | × | × |
private | √ | × | × | × |
28. StringBuffer与StringBuilder的区别
- StringBuffer和StringBuilder类都表示内容可以被修改的字符串,StringBuilder是线程不安全的,运行效率高,如果一个字符串变量是在方法里面定义,这种情况只可能有一个线程访问它,不存在不安全的因素了,则用StringBuilder。如果要在类里面定义成员变量,并且这个类的实例对象会在多线程环境下使用,那么最好用StringBuffer
29. heap和stack有什么区别
- java的内存分为两类,一类是栈内存,一类是堆内存。栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
- 堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中
30. 深拷贝和浅拷贝
- 如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝
- 浅拷贝: 对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝
- 深拷贝: 对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容
- 总结: 浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作
31. CAS
- cas是一种基于锁的操作,而且是乐观锁机制。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高
- Compare and Swap,比较并操作,指的是将预期值与当前变量的值比较(compare),如果相等则使用新值替换(swap)当前变量,否则不作操作
- CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
32. 构造器能否被重写?
- 构造器是不能被继承的,因为每个类的类名都不相同,而构造器名称与类名相同,所以根本谈不上继承。
- 又由于构造器不能继承,所以就不能被重写。但是,在同一个类中,构造器是可以被重载的
33. 查看死锁
- Jconsole
- Jstack:Jstack -l/-F 进程号
34. 引用
- 强引用: 只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象
- 软引用:只有在内存不足的时候JVM才会回收该对象。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中SoftReference<String> sr = new SoftReference<String>(new String("hello"));
- 弱引用: 当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
WeakReference<String> sr = new WeakReference<String>(new String("hello"));
- 虚引用: 如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收
ReferenceQueue<String> queue = new ReferenceQueue<String>(); PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
35. 产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不可抢占:进程已获得的资源,在末使用完之前,不能强行剥夺
- 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系
36. 避免死锁
- 加锁顺序,确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生
- 加锁时限,尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试
- 死锁检测,主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景
37. 公平锁、非公平锁
- 公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
- 非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
非公平锁性能比公平锁高5~10倍,因为公平锁需要在多核的情况下维护一个队列
Java中的ReentrantLock 默认的lock()方法采用的是非公平锁
38. Synchronized实现原理
- 显示同步,monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器置为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令
- 修饰同步方法: 隐式同步,不是由 monitorenter 和 monitorexit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用
- notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因.
39. 使⽤ synchronized 修饰静态⽅法和⾮静态⽅法有什么区别
- 当修饰静态方法的时候,锁住的是当前类的Class
- 当修饰非静态方法的时候,锁住的是当前的实例对象this
- 当修饰一个obj对象的时候,锁住的就是obj对象的修改本身
40. 锁的状态
锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级
- 无锁
- 偏向锁,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能
- 轻量级锁,倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁
- 自旋锁,轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了
- 重量级锁
41. 谈谈volatile的作用?实现原理以及使用场景
-
①volatile只能保证多线程三大特性中的可见性和有序性。
1)可见性:每个线程都有一个自己的本地内存,对于共享变量,线程每次读取和写入的都是共享变量在本地内存中的副本,然后在某个时间点将本地内存和主内存的值进行同步。而当修改volatile修饰的变量后,强制把对变量的修改同步到主内存。而其它线程在读取自己的本地内存中的值的时候,发现是valotile修饰的且已经被修改了,会把自己本地内存中的值置为无效,然后从主内存中读取。
2)有序性:在执行程序时,为了提高性能,处理器和编译器常常会对指令进行重排序,这种重排序一般只能保证单线程下执行结果不被改变。当被volatile修饰的变量后,将会禁止重排序。 -
②代码层面实现:通过内存屏障来实现的。所谓的内存屏障,是在某些指令中插入屏障指令。虚拟机读取到这些屏障指令时主动将本地内存的变量值刷新到内存,或直接从主内存中读取变量的值。通过屏障指令会禁止屏障前的操作命令和屏障后的命令进行重排序。系统层面实现:在多处理器下,保证各个处理器的缓存是一致的,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态。
-
③1:多线程间状态标识;2:单例模式中双重检查锁的写法;3:定期观察成员变量状态的方法
42. 线程与进程
- 进程是系统进行资源调度和分配的一个基本单位,线程是进程的实体,是CPU调度和分派的基本单位
- 一个进程可以有多个线程,多个线程也可以并发执行
- 线程不能看做独立应用,而进程可看做独立应用
- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径,没有独立的地址空间,多进程的程序比多线程的程序健壮
- 进程的切换比线程切换开销大
43. 如何停止线程?
- 主线程提供volatile boolean flag,线程内while判断flag
- 线程内while(!this.isInterrupted),主线程里调用interrupt()方法,通知线程应该中断了
- 将一个线程设置为守护线程后,当进程中没有非守护线程后,守护线程自动结束
44. 线程实现方式
- extends Thread
public class ThreadCreateTest { public static void main(String[] args) { new MyThread().start(); } class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId()); } } }
- implements Runnable,创建Thread类对象传递参数
public class RunableCreateTest { public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); new Thread(runnable).start(); } class MyRunnable implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId()); } } }
- 通过Callable和Future创建线程
public class CallableCreateTest { public static void main(String[] args) throws Exception { // 将Callable包装成FutureTask,FutureTask也是一种Runnable MyCallable callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable); new Thread(futureTask).start(); // get方法会阻塞调用的线程 Integer sum = futureTask.get(); System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum); } class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting..."); int sum = 0; for (int i = 0; i <= 100000; i++) { sum += i; } Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover..."); return sum; } } }
45. 开启线程写法
Thread t = new Thread(new Runnable() {
@Override
public void run() {
}
});
t.start();
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
// lamboda表达式,可以不用重写run方法
Thread t1 = new Thread(() -> {
System.out.println("hh");
});
t1.start();
new Thread(() -> {
System.out.println("hh");
}).start();
46 同一线程多次调用start方法
线程首先会运行一次,然后抛出java.lang.IllegalThreadStateException
异常
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
第一次运行start()方法时,threadStatus是0,此时if条件不满足,继续执行,会将当前线程添加到线程组中去执行。第二次运行start()方法时,threadStatus变成了2,if条件满足,于是抛出了java.lang.IllegalThreadStateException异常
47. Java自动装箱与拆箱
//boolean原生类型自动装箱成Boolean
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
//byte原生类型自动装箱成Byte
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
//byte原生类型自动装箱成Byte
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
//char原生类型自动装箱成Character
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
//int原生类型自动装箱成Integer
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//long原生类型自动装箱成Long
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
//double原生类型自动装箱成Double
public static Double valueOf(double d) {
return new Double(d);
}
//float原生类型自动装箱成Float
public static Float valueOf(float f) {
return new Float(f);
}
Integer b=3
:3是int类型,如果要把int类型赋值给Integer包装类,就需要自动装箱,相当于Integer b=Integer.ValueOf(3)
48. 基本数据类型的特点,最大值和最小值
基本类型:int 二进制位数:32
包装类:java.lang.Integer
最小值:Integer.MIN_VALUE= -2147483648 (-2的31次方)
最大值:Integer.MAX_VALUE= 2147483647 (2的31次方-1)
注意: 除double 和float 两种类型以外,其他基本类型入Integer值 在 -128 ~ 127之间时不会新建一个Integer 对象而是从缓存中获取。所以在做 == 判断时 要注意值的大小,如果超过范围,则两个值即使一样但 == 比较的结果还是false
49. 什么是反射
- Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制
50. 反射的方法
- getDeclaredMethod:获取类的所有方法,不能获取继承方法
- getMethod:只能获取公有方法,但包括继承的方法
51. loadClass和forName的区别
- CLass.forName得到的class是已经初始化完成的。eg:加载MySQL驱动需要执行驱动中的static代码
- CLassLoader.loadClass得到的class是还没有链接,还没有初始化
52. 谈谈如何通过反射创建对象
- 通过Class字节码对象newInstance();-+通过类对象调用newInstance()方法,例如:String.class.newInstance()
- 通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance(“Hello”)
53. ArrayList是否会越界?
- 会,在多线程下
54. 为什么集合类没有实现Cloneable和Serializable接口?
- 克隆(cloning)或者序列化(serialization)的语义和含义是跟具体的实现相关的。因此应该由集合类的具体实现类来决定如何被克隆或者序列化
55. Iterator和ListIterator的区别是什么?
- Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List
- Iterator对集合只能是顺序向后遍历,ListIterator既可以向前也可以向后
- ListIterator实现了Iterator接口,幷包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等
56. 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
- 快速失败(fail-fast)
在使用迭代器对集合对象进行遍历的时候,如果A线程对集合进行遍历,正好B线程对集合进行修改(增加、删除、修改)则A线程会抛出ConcurrentModificationException异常
**原理:**迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历 - 安全失败(fail-safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历 - 原理: 由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception,例如CopyOnWriteArrayList
线程创建有很大开销,怎么优化?
- 使用线程池
57. Java中有几种线程池?
java里面的线程池的顶级接口是Executor,Executor并不是一个线程池,而只是一个执行线程的工具,而真正的线程池是ExecutorService
- newCachedThreadPool 缓存型线程池,在核心线程达到最大值(Interger. MAX_VALUE)之前,有任务进来就会创建新的核心线程,并加入核心线程池,即时有空闲的线程,也不会复用
- newFixedThreadPool 建立一个线程数量固定的线程池,规定的最大线程数量,超过这个数量之后进来的任务,会放到等待队列中,如果有空闲线程,则在等待队列中获取,遵循先进先出原则
- newScheduledThreadPool 计划型线程池,可以设置固定时间的延时或者定期执行任务,同样是看线程池中有没有空闲线程,如果有,直接拿来使用,如果没有,则新建线程加入池
- newSingleThreadExecutor 建立一个只有一个线程的线程池,如果有超过一个任务进来,只有一个可以执行,其余的都会放到等待队列中,如果有空闲线程,则在等待队列中获取,遵循先进先出原则
58. 线程池参数
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:空闲的线程保留的时间
- TimeUnit:空闲线程的保留时间单位
- BlockingQueue:阻塞队列,存储等待执行的任务
- ThreadFactory:线程工厂,用来创建线程
- RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常处理策略
- allowCoreThreadTimeout:是否允许核心线程空闲退出,默认值为false
59. 线程池按以下行为执行任务
- 当线程数小于核心线程数时,创建线程
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满
① 若线程数小于最大线程数,创建线程
② 若线程数等于最大线程数,抛出异常,拒绝任务
60. 概括的解释下线程的几种可用状态
- NEW:新建,线程刚创建,尚未启动
- RUNNABLE:运行,在JVM中正在运行的线程,其中运行状态可以有运行中RUNNING和READY两种状态,由系统调度进行状态改变
- WAITING:无限期等待,不会被CPU分配执行时间,需要显示唤醒
- TIME_WAITING:限期等待,在一定时间后会有系统自动唤醒
- TERMINATED:结束,线程执行完毕,已经退出
61. 讲一下非公平锁和公平锁在reetrantlock里的实现
- 非公平锁: 当线程争夺锁的过程中,会先进行一次CAS尝试获取锁,若失败,则进入acquire(1)函数,进行一次tryAcquire再次尝试获取锁,若再次失败,那么就通过addWaiter将当前线程封装成node结点加入到Sync队列,这时候该线程只能乖乖等前面的线程执行完再轮到自己了。
- 公平锁: 当线程在获取锁的时候,会先判断Sync队列中是否有在等待获取资源的线程。若没有,则尝试获取锁,若有,那么就那么就通过addWaiter将当前线程封装成node结点加入到Sync队列中
62. 反射的实现与作用
- 通过获取类的字节码,将字节码加载到内存中,在程序运行时生成一个该类的实例
- 作用: 得到一个对象所属的类,运行时创建一个类,获取一个类的所有成员变量与方法,在运行时调用对象的方法
63. 给我一个你最常见到的runtime exception
NullPointerExceptio
空指针异常
SecurityException
安全异常
IndexOutOfBoundsException
下标越界异常
ClassNotFoundException
类没找到时,抛出该异常
ClassCastException
类型强制转换异常
64. Java元注解
- @Retention
表明该注解的生命周期 - @Target
表明该注解可以应用的java元素类型(方法、变量…) - @Document
说明该注解将被包含在javadoc中 - @Inherited
表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解
65. Serializable中的ID有什么作用
- 反序列化的时候使用,若不指定serialVersionUID,编译器会根据类的内部实现计算一个,反序列化时,会把对象UID和类UID比较,相同,可以反序列化,不同,不能序列化
66. this, super
- this()函数主要应用于同一类中从某个构造函数调用另一个重载版的构造函数。this()只能用在构造函数中,并且也只能在第一行。所以在同一个构造函数中this()和super()不能同时出现
- super()函数在子类构造函数中调用父类的构造函数时使用,而且必须要在构造函数的第一行
67. String为什么要用final修饰
- 使用final修饰实现字符串池,节约heap空间,不同的字符串变量指向同一字符串
- 线程安全,同一个字符串可以被多个线程共享,不会因为线程安全问题使用同步
- 安全性,加载时不被改变
- 因为字符串不可变,在创建的时候hashcode就被缓存
68. Java8新特性
- Lambda表达式
- 新的时间API
- 函数式编程
- default关键字
69. Java中是否可以覆盖(override)一个private或者是static的方法?
- Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用
70. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
- 值传递。Java语言的方法调用只支持参数的值传递。基本数据类型传递副本,引用数据类型传递栈中地址的值,真正的对象存放在堆内存中
71. String s = new String(“xyz”);创建了几个字符串对象?
- 两个对象,一个是常量池的"xyz",一个是用new创建在堆上的对象
72. 在 Java 中,如何跳出当前的多重嵌套循环
- 定义一个标号,break + 标号
ok: for(int i=0;i<10;i++) { for(int j=0;j<10;j++) { System.out.println(“i=” + i + “,j=” + j); if(j == 5) break ok; } }
- 让外层的循环条件表达式的结果可以受到里层循环体代码的控制
for(int i=0;i<arr.length && !found;i++) { for(int j=0;j<arr[i].length;j++){ System.out.println(“i=” + i + “,j=” + j); if(arr[i][j] == 5) { found = true; break; } } }
73. Java中创建对象的五种方式
-
使用new关键字
Employee emp1 = new Employee();
-
使用Class类的newInstance方法
这个newInstance方法调用无参的构造函数创建对象Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee").newInstance(); Employee emp2 = Employee.class.newInstance();
-
使用Constructor类的newInstance方法
和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数Constructor<Employee> constructor = Employee.class.getConstructor(); Employee emp3 = constructor.newInstance();
-
使用clone方法
无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。 -
使用反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj")); Employee emp5 = (Employee) in.readObject();
74. 内存泄露问题
- 静态集合类: 如 HashMap、Vector 等集合类的静态使用最容易出现内存泄露,因为这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放
- 各种资源连接包括数据库连接、网络连接、IO连接等没有显式调用close关闭
- 监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露
75. ThreadLocal内存泄露?
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露
76. ThreadLocal内存泄漏根源
由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key的value就会导致内存泄漏,而不是因为弱引用
77. ThreadLocal内存泄露解决办法
每次使用完ThreadLocal,都调用它的remove()方法,清除数据
78. 线程池调优?
- 设置最大线程数,防止线程资源耗尽
- 使用有界队列,从而增加系统的稳定性和预警能力(饱和策略)
- 根据任务的性质设置线程池大小:CPU密集型任务(CPU个数个线程),IO密集型任务(CPU个数两倍的线程),混合型任务(拆分)
HashMap
1. 原理
- HashMap最多只允许一条Entry的键为Null(多条会覆盖),但允许多条Entry的值为Null
- 若负载因子越大,那么对空间的利用更充分,但查找效率的也就越低;若负载因子越小,那么哈希表的数据将越稀疏,对空间造成的浪费也就越严重。系统默认负载因子0.75
- 调用put方法存值时,HashMap首先会调用Key的hashCode方法,然后基于此获取Key哈希码,通过哈希码快速找到某个桶,这个位置可以被称之为bucketIndex.如果两个对象的hashCode不同,那么equals一定为false;否则,如果其hashCode相同,equals也不一定为 true。所以,理论上,hashCode可能存在碰撞的情况,当碰撞发生时,这时会取出bucketIndex桶内已存储的元素,并通过hashCode() 和 equals()来逐个比较以判断Key是否已存在。如果已存在,则使用新Value值替换旧Value值,并返回旧Value值;如果不存在,则存放新的键值对<Key, Value>到桶中。因此,在 HashMap中,equals() 方法只有在哈希码碰撞时才会被用到
- jdk1.8中,首先,对key进行hash运算,为空,hash值为0,如果table[0]没有数据,直接插入,如果有数据,则进行数据的更新;若不为空,则先计算key的hash值,然后和table.length -1 & 运算找到在数组中的索引位置,如果table数组在该位置处有元素,则查找是否存在相同的key,若存在则覆盖原来key的value,否则将该元素保存在链尾。此外,若table在该处没有元素,则直接保存
2. hash()和indexFor()
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1); // 作用等价于取模运算,但这种方式效率更高
}
- hash() 方法用于对Key的hashCode进行重新计算,而 indexFor()方法用于生成这个Entry对象的插入位置。当计算出来的hash值与hashMap的(length-1)做了&运算后,会得到位于区间[0,length-1]的一个值。特别地,这个值分布的越均匀,HashMap 的空间利用率也就越高,存取效率也就越好,保证元素均匀分布到table的每个桶中以便充分利用空间
- hash():使用hash()方法对一个对象的hashCode进行重新计算是为了防止质量低下的hashCode()函数实现。由于hashMap的支撑数组长度总是2 的幂次,通过右移可以使低位的数据尽量的不同,从而使hash值的分布尽量均匀
- indexFor():保证元素均匀分布到table的每个桶中; 当length为2的n次方时,h&(length -1)就相当于对length取模,而且速度比直接取模要快得多,这是HashMap在速度上的一个优化
3. 扩容resize()和重哈希transfer()
- 当已使用75%的容量时会进行扩容,0.75是扩容因子。扩容因子大,则空间利用率高,但容易发生hash地址冲突;扩容因子小,浪费空间
- 如果e的hash值与老表的容量进行与运算为0,则扩容后的索引位置跟老表的索引位置相同
- 如果e的hash值与老表的容量进行与运算为1,则扩容后的索引位置为:老表的索引位置+oldCap
- 为了保证HashMap的效率,系统必须要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于threshold(table数组长度*加载因子)
- 重哈希的主要是一个重新计算原HashMap中的元素在新table数组中的位置并进行复制处理的过程
4. HashMap的底层数组长度为何总是2的n次方
- 当底层数组的length为2的n次方时, h&(length - 1) 就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化
- 不同的hash值发生碰撞的概率比较小,这样就会使得数据在table数组中分布较均匀,也就是说碰撞的机率小,空间利用率较高,查询速度也较快
5. hashmap负载因子为什么是0.75
若加载因子越大,填满的元素越多,好处是,空间利用率高了,但冲突的机会加大了,链表长度会越来越长,查找效率降低。
反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)
冲突的机会越大,则查找的成本越高。因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷.
Hashtable
Hashtable的默认容量(数组大小)为11,默认的负载因子为0.75
1. put
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
- hashtable之所以是线程安全的,原因为在put和get方法上使用synchronized关键字进行修饰
- 限制了value不能为null
- 由于直接使用key.hashcode(),而没有向hashmap一样先判断key是否为null,所以key为null时,调用key.hashcode()会出错,所以hashtable中key不能为null
- 遍历table[index]所连接的链表,查找是否已经存在key与需要插入的key值相同的节点,如果存在则直接更新value,并返回旧的value
- 如果table[index]所连接的链表上不存在相同的key,则通过addEntry()方法将新节点加载链表的开头
2. Hashtable和hashmap的区别
- hashmap中key和value均可以为null,但是hashtable中key和value均不能为null
- hashmap采用的是数组(桶位)+链表+红黑树结构实现,而hashtable中采用的是数组(桶位)+链表实现
- hashmap中数组容量的大小要求是2的n次方,如果初始化时不符合要求会进行调整,而hashtable中数组容量的大小可以为任意正整数
- hashmap中的寻址方法采用的是位运算按位与,而hashtable中寻址方式采用的是求余数
- hashmap不是线程安全的,而hashtable是线程安全的
- hashmap中默认容量的大小是16,而hashtable中默认数组容量是11
- hash冲突时,hashmap尾插法,hashtable头插法
ConcurrentHashMap
1. put(), get()
- 不允许key值为null,也不允许value值为null
- HashTable 和由同步包装器包装的HashMap每次只能有一个线程执行读或写操作,ConcurrentHashMap 在并发访问性能上有了质的提高。在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设置为 16),及任意数量线程的读操作
2. ConcurrentHashMap是如何保证线程安全的
并发控制使用Synchronized和CAS来操作
cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高
如果数组没有值的话,使用封装的原子操作插入值,如果数组有值需要操作链表,使用Synchronized对需要操作的节点Node上锁
数据库
MySQL
1. 主键,唯一索引区别
- 主键一定会创建一个唯一索引,但是有唯一索引的列不一定是主键
- 主键不允许为空值,唯一索引列允许空值;
- 一个表只能有一个主键,但是可以有多个唯一索引;
- 主键可以被其他表引用为外键,唯一索引列不可以;
- 主键是一种约束,而唯一索引是一种索引,是表的冗余数据结构,两者有本质的差别
2. 索引失效
- 字符串不加单引号
- 在 where 子句中对字段进行 null 值判断
- like查询以%开头
- 对于多列索引,不是使用的第一部分,则不会使用索引
- where 子句中使用 or 来连接条件
- 使用mysql内部函数导致索引失效
- where 子句中的“=”左边进行函数、算术运算或其他表达式运算
3. Hash索引
- 不支持范围查询,仅支持精确查询
- 只支持建索引时使用到字段的查询
- 无法运用索引的数据排序
- 无法利用部分索引键查询
- 不能避免全表扫描
4. 红黑树为什么不用在MySQl
- IO影响,使用红黑树会造成过多的磁盘IO,每一次取4k,浪费磁盘空间
5. HashMap为什么使用红黑树
- 放在内存中,运算很快
6. drop delete truncate区别
- DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作
- TRUNCATE TABLE
则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快 - TRUNCATE 和DELETE只删除数据, DROP则删除整个表(结构和数据)
7. 索引种类
- 密集索引(主键索引和数据存放在一起): 主键索引
- 稀疏索引(主键和数据分开存放,需要找两次): 普通索引,唯一索引,组合索引,全文索引
8. 添加索引
ALTER TABLE
-- 1.添加PRIMARY KEY(主键索引)
ALTER TABLE table_name ADD PRIMARY KEY (column) ;
-- 2.添加UNIQUE(唯一索引)
ALTER TABLE table_name ADD UNIQUE (column);
-- 3.添加INDEX(普通索引)
ALTER TABLE table_name ADD INDEX index_name (column);
-- 4.添加FULLTEXT(全文索引)
ALTER TABLE table_name ADD FULLTEXT (column);
-- 5.添加多列索引
ALTER TABLE table_name ADD INDEX index_name (column1, column2, column3);
CREATE INDEX
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
9. 查看已创建索引
SHOW INDEX FROM table_name;
10. 删除索引
DROP INDEX index_name ON table_name;
或
ALTER TABLE table_name DROP INDEX index
11. mysql的辅助索引
辅助索引就是B+树的非叶子节点
12. Innodb和MyISAM
- Innodb
- 支持事务
- 支持行级锁
- 默认主键索引为聚密集索引
- MyISAM
- 读多写少
- 表级锁
- 不支持外键
- myisam在磁盘存储上有三个文件,每个文件名以表名开头,扩展名指出文件类型,.frm 用于存储表的定义,.MYD 用于存放数据,.MYI 用于存放表索引
13. 共享锁,排他锁
- InnoDB普通 select 语句默认不加锁(快照读,MYISAM会加锁),而CUD操作默认加排他锁
- MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) (注:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。
- 多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读.
- 在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本(有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录
- SELECT … LOCK IN SHARE MODE :共享锁(S锁,share locks)。其他事务可以读取数据,但不能对该数据进行修改,直到所有的共享锁被释放
- SELECT … FOR UPDATE:排他锁(X锁,exclusive locks)。如果事务对数据加上排他锁之后,则其他事务不能对该数据加任何的锁。获取排他锁的事务既能读取数据,也能修改数据。
- InnoDB默认隔离级别 可重复读(Repeated Read)
- 操作字段未加索引(主键索引、普通索引等)时,使用表锁
- InnoDB行级锁基于索引实现
- 索引数据重复率太高会导致全表扫描:当表中索引字段数据重复率太高,则MySQL可能会忽略索引,进行全表扫描,此时使用表锁。可使用 force index 强制使用索引
14. 悲观锁、乐观锁
- 悲观锁:
每次去拿资源的时候都认为别人会修改,所以每次在拿资源的时候都会上锁,这样别人想拿这个资源就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁 - 乐观锁:
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁
使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号
当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据
15. 隔离级别
- Read Uncommitted(未提交读): 在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取事务未提交的数据,也被称之为脏读
- Read Committed(已提交读): 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别会造成不可重复读,同一事物读取的结果可能会不一样,因为同一事务的其他实例在该实例处理其间可能会有新的提交修改,偏修改
- Repeatable Read(可重复读): MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个问题:幻读。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行,偏插入
- Serializable(串行化): 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。这个级别,可能导致大量的超时现象和锁竞争
16. InnoDB和MyISAM的区别?
- InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务
- InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败
- InnoDB是密集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的
- InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快
- Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高,但是innodb可以使用sphinx插件支持全文索引,并且效果更好
17. 三范式
第一范式:一个关系模式中所有属性都是不可分的
第二范式:满足第一范式,且非主属性完全依赖主键
第三范式:满足第二范式,属性不依赖于其它非主属性,消除传递依赖
18. ACID
原子性,一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节
一致性,在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态
隔离性,一个事务所做的修改在最终提交前,对其他事务是不可见的
持久性,一旦事物提交,则其所做的修改就会永远保存在数据库中
19. 数据库锁的分类
- 锁的粒度:表级锁、行级锁、页级锁
- 锁级别:共享锁、排它锁
- 加锁方式:自动锁、显示锁
- 操作划分:DML锁(操作数据:增删改查)、DDL锁(变更表结构)
- 使用方式:乐观锁、悲观锁(不光是数据库,程序中也存在)
20. 当前读和快照读
- 当前读:select…lock in share mode、select…for update
- 当前读:update、insert、delete
- 快照读:不加锁的非阻塞读,select
21. group by 语法
使用group by,select不能出现使用group by以外的列,除非使用函数,对同一张表成立
select id, count(score) from ... group by id √
select id, count(score) course_id from ... group by id ×
group by 里出现的某个列,select里要么是group by里出现的列,要么是别的表的列或者带有函数的列
22. Having
- 常与group by字句一起使用,用于在group by后指定过滤的条件,如果省略group by,having字句和where功能一样,语法和where一样
- where过滤行,having过滤组
- 出现在同一sql的顺序:where > group by > having
- 在having中使用count(*)计算的是每个组的个数
23. char和varchar区别
- CHAR的长度是固定的,而VARCHAR的长度是可以变化的, 比如,存储字符串“abc",对于CHAR (10),表示你存储的字符将占10个字节(包括7个空字符),而同样的VARCHAR2 (10)则只占用3个字节的长度,10只是最大值,当你存储的字符小于10时,按实际长度存储
Redis
1. Redis的数据结构
- String、Hash、List、Set、Sorted Set
2. Sorted Set
- 在有序集中,用于排序的值叫做score,实际存储的值叫做member
3. Hash底层结构
- redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable
4. 持久化
- RDB持久化
是指把当前进程数据生成快照保存到硬盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。触发RDB持久化过程分为手动触发和自动触发
**优点:**代表Redis在某一个时间点上的数据快照,适合全量同步;恢复数据快于AOF
**缺点:**无法做到实时持久化/秒级持久化
牺牲性能,缓存一直 - AOF持久化
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录。工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)
**优点:**实时同步
**缺点:**效率慢,文件大
5. 讲一下redis的主从复制怎么做的?
- 全量同步: 一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份
- 增量同步: Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令
6. Redis主从同步策略
- 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步
7. redis为什么读写速率快性能好?
- 存内存操作。数据存在内存中,执行效率高
- 单线程操作。避免了频繁的上下文切换
- 采用非阻塞IO多路复用机制。内部实现采用epoll,采用了epoll+自己实现的简单的事件框架(事件分离器),内部采用非阻塞的执行方式,吞吐能力比较大。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间
- 数据结构简单。使用特殊数据结构,对数据存储进行了优化
8. redis为什么是单线程?
- 为了不让cpu成为redis的瓶颈,而单线程方便管理、容易实现,且不用锁,所以使用单线程,利用队列技术将并发访问变为串行访问
9. 缓存的优点?
- 减少数据库读操作,降低数据库压力;数据从内存中读取,加快响应速度
10. 缓存雪崩
- 在某一个时间段,缓存集中过期失效,而查询数据量巨大,引起数据库压力过大甚至宕机;缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
- 解决方法: 分散过期时间
11. 缓存穿透
- 概念:不走缓存直接查询数据库(查询一个数据库一定不存在的数据)
- eg:传入的主键参数为-1,会是怎么样?这个-1,就是一定不存在的对象。就会每次都去查询数据库,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。即便是采用UUID,也是很容易找到一个不存在的KEY,进行攻击。
- 解决方案: 对某一个key,缓存空值value,时间设置短一点
12. 缓存击穿
- 指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力;缓存击穿指并发查同一条数据
- 解决方案:
- 获取数据步骤据加上互斥锁
13. Redis数据的恢复
14. 如何通过Redis实现分布式锁
set key value [Ex seconds][Px millseconds][NX|XX]
set lock 12345 ex 10 nx
也可以使用SENTX和EXPIRE组合使用,但是这两个步骤不能保证原子性,不推荐使用
计网
HTTP
1. 说一说四种会话跟踪技术
- URL重写
- 隐藏表单域
- cookie
- session
2. 浏览器输入网址后发生了什么?
- 第一步: 在浏览器中输入url后,应用层会使用DNS解析域名,如果本地存有对应的IP,则使用;如果没有,则会向上级DNS服务器请求帮助,直至获得IP,应用层将请求的信息装载入HTTP请求报文,然后应用层将发起HTTP请求。
- 第二步: 传输层接收到应用层传递下来的数据,把HTTP会话请求分成报文段,添加源和目的端口,并为它们编号,方便服务器接收时能准确地还原报文信息。通过三次握手和目标端口建立安全通信。
- 第三步: 网络层接收传输层传递的数据,根据IP通过ARP协议获得目标计算机物理地址—MAC。当通信的双方不在同一个局域网时,需要多次中转才能到达最终的目标,在中转的过程中需要通过下一个中转站的MAC地址来搜索下一个中转目标。
- **第四步:**找到目标MAC地址以后,就将数据发送到数据链路层,这时开始真正的传输请求信息,传输完成以后请求结束
- 第五步: 服务器接收数据后,从下到上层层将数据解包,直到应用层
- 第六步: 服务器接收到客户端发送的HTTP请求后,查找客户端请求的资源,将数据装载入响应报文并返回,响应报文中包括一个重要的信息——状态码,如200,404,500
3. 常见状态码
状态码 | 英文描述 | 中文描述 |
---|---|---|
100 | 继续 | |
101 | 协议升级 | |
200 | OK | 请求成功 |
301 | 永久性转移。该状态码表示请求的资源已经重新分配 URI,以后应该使用资源现有的 URI | |
302 | 临时移动。该状态码表示请求的资源已被分配了新的 URI,希望用户(本次)能使用新的 URI 访问 | |
304 | 请求资源未修改 | |
307 | 临时重定向 | |
400 | Bad Request | 客户端请求语法错误 |
401 | 要求认证 | |
403 | 拒绝请求 | |
404 | 没有找到客户端请求资源 | |
500 | 服务器内部错误 | |
502 | Bad Gateway | 错误网关,代理服务器连接不到服务器 |
503 | 服务不可用 | |
504 | Gateway Time-out | 请求超时,代理服务器请求服务器超时 |
4. sessionStorage 、localStorage 和 cookie 之间的区别
- 共同点: 都是保存在浏览器端,且同源的
- 区别:
HTML5 提供- cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递;cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。存储大小限制也不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识
- 而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
- 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
- 作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。Web Storage 的 api 接口使用更方便
5. Cookie,Session区别
- cookie存放在浏览器上,session在服务器上,都是存储访问者信息
- cookie容易被截获不安全,session在服务器安全。cookie容量小,session容量大
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
- 如果我们需要经常登录一个站点时,最好用cookie来保存信息 ;如果对于需要安全性高的站点以及控制数据的能力时需要用session
6. TCP三次握手
-
第一次: 客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号 seq=x,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这个三次握手中的开始。表示客户端想要和服务端建立连接。
-
第二次: TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号 seq=y,此
时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端
是否准备好。 -
第三次: 客户端收到请求,发送确认,将确认信号ACK置为1,确认号为y+1,随机号seq为x+1。
TCP客户进程收到确认后,还要向服务器给出确认。确认报文的确认信号ACK=1,ack=y+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。
TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。这里客户端表示我已经准备好。
7. TCP四次挥手
- 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。
- 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1。
- 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
- 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手
8. 为什么是三次握手不是两次握手 - 防止出现请求超时导致脏连接。如果是两次,一个超时的连接请求到达服务器,服务器会以为是客户端创建的连接请求,然后同意创建连接,而客户端不是等待确认状态,丢弃服务器确认数据,导致服务器单方面创建连接。如果是三次,服务器没有收到客户端的确认信息,最终超时导致连接创建失败,因此不会出现脏连接
9. 为什么是四次挥手
- TCP是全双工通信,服务器和客户端都可以发送和接受数据,客户端告诉服务器断开连接,等待服务器确认,两次握手,服务器处理完数据后,向客户端发送断开连接信号,等待客户端的确认,四次握手
10. 如果已经建立了连接,但是客户端突然出现故障了怎么办?
- TCP还设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
11. 为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
- 虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
12. 单工、半双工、全双工
- 单工:只能有一个方向的通信而没有反方向的交互
- 半双工:通信双方都可以发送信息,但不能同时发送
- 全双工:通信双方都可以同时发送和接收信息
13. 传输层和网络层的区别
- 网络层为不同的主机提供通信服务,传输层为不同应用进程提供通信服务。
- 网络层只对报文头部进行差错检测,而传输层对整个报文进行差错检测。
14. 端口的作用和理解
- 在网络技术中,端口大致有两种意思:
一是物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口。 - 二是逻辑意义上的端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。
主机通过端口来为外界提供服务,各个不同的应用通过IP + 不同的端口号来区分不同的服务
15. 广播 单播 组播
- 单播(unicast):是指封包在计算机网络的传输中,目的地址为单一目标的一种传输方式。它是现今网络应用最为广泛,通常所使用的网络协议或服务大多采用单播传输,例如一切基于TCP的协议。
- 组播(multicast):也叫多播,多点广播或群播。指把信息同时传递给一组目的地址。它使用策略是最高效的,因为消息在每条网络链路上只需传递一次,而且只有在链路分叉的时候,消息才会被复制。
- 广播(broadcast):是指封包在计算机网络中传输时,目的地址为网络中所有设备的一种传输方式。实际上,这里所说的“所有设备”也是限定在一个范围之中,称为“广播域”。
16. UDP(User Data Protocol)用户数据报协议
- 无连接
- 不可靠(不能保证都送达)
- 面向报文(UDP数据传输单位是报文,不会对数据进行拆分和拼接操作,只是给上层传来的数据加个UDP头或者给下层来的数据去掉UDP头)
- 没有拥塞控制,始终以恒定速率发送数据
- 支持一对一、一对多、多对多、多对一
- 数据包报文首部开销小,只有8字节
17. TCP(Transmission Control Protocol)传输控制协议
- 有连接
- 可靠的
- 面向字节
- 全双工通信,TCP两端既可以作为发送端也可以作为接收端
- 连接的两端只能是两个端点,即一对一,不能一对多
- 至少20个字节,比UDP大的多
18. TCP/IP每一层对应的协议
- 应用层:FTP(文件传送协议)、Telnet(远程登录协议)、DNS(域名解析协议)、SMTP(邮件传送协议)、POP3(邮局协议)、HTTP、NFS
- 传输层:UDP、TCP
- 网络层:IP、ICMP、ARP、RARP、OSFP
- 数据链路层:PPP、FR、HDLC、VLAN、MAC
- 物理层:RJ45、CLOCK、IEEE802.3
19. TCP/UDP对应协议
- TCP对应协议:FTP(文件传输协议)、Telnet(远程登录)、SMTP(发送邮件)、POP3(接收邮件)、HTTP
- UDP对应协议:DNS(域名解析服务)、SNMP(简单网络管理协议)、TFTP(简单文件传输协议)
20. 面向连接和非面向连接的服务的特点是什么?
- 面向连接的服务,通信双方在进行通信之前,要先在双方建立起一个完整的可以彼此沟通的通道,在通信过程中,整个连接的情况一直可以被实时地监控和管理。
- 非面向连接的服务,不需要预先建立一个联络两个通信节点的连接,需要通信的时候,发送节点就可以往网络上发送信息,让信息自主地在网络上去传,一般在传输的过程中不再加以监控。
21. ARP协议工作
- (1)首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示P地址和MAC地址之间的对应关系
- (2)当源主机要发送数据时,首先检查ARP列表中是否有对应P地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机P地址,源主机MAC地址,目的主机的P地址
- (3)当本网络的所有主机收到该ARP数据包时,首先检查数据包中的P地址是否是的P地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的P和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址
- (4)源主机收到ARP响应包后。将目的主机的P和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败广播发送ARP请求,单播发送ARP响应
22. 进程和线程的区别:
线程是指进程内的一个执行单元,也是进程内的可调度实体,与进程的区别:
- (1)调度线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
- (2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
- (3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源
- (4)系统开销在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销
23. 进程间通信方式
- 管道、信号、信号量、socket、共享内存、消息队列,信号是使用信号处理器进行、信号量使用P、V操作
24. 进程进入等待状态(就绪状态)有哪几种方式?
- CPU调度给优先级更高的 Thread(线程),原先Thread进入Waiting(等待)状态。阻寨的Thread获得资源或者信号,进入Waiting状态。在时间片轮转的情况下,如果时间片到了,也将进入等待状态。
25. GET、POST区别
- Http报文层面:GET请求将请求信息放在URL,POST放在报文体中,GET请求本身没有url长度限制,但浏览器有,所以有数据大小限制,而post没有
- 数据库层面:GET符合幂等性和安全性(同样请求结果一样,不会改变数据),POST不符合
- 其他层面:GET请求可以被缓存、存储,GET请求可以保存在浏览器的浏览记录中后者保存为书签,POST不行
26. TCP、UDP区别
- TCP是面向连接的,UDP是无连接的
- TCP是可靠的,UDP是不可靠的
- TCP只支持点对点通信,UDP支持一对一、一对多、多对一、多对多的通信模式
- TCP是面向字节流的,UDP是面向报文的
- TCP有拥塞控制机制;UDP没有拥塞控制,适合媒体通信
- TCP首部开销(20个字节)比UDP的首部开销(8个字节)要大
27. TCP为什么可靠
- 确认和重传机制: 确认丢失、确认迟到、超时重传(停止等待协议)
- 校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段
- 流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制和乱序重排)
- 拥塞控制: 当网络拥塞时,减少数据的发送
- 有序: 未按序到达的数据确认会重新发送
28. forward和redirect区别
-
请求转发(Forward),客户端器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,转发页面和转发到的页面可以共享request里面的数据
是服务器内部的动作,服务器直接转到其他url,使用其中的资源,这个过程客户端是不可见的,客户端请求的url不会发生变化。
过程:客户端发起http请求-->服务器接受http请求--->服务器内部调用方法完成请求处理和转发--->将请求的资源发送给客户端。转发时,服务器只能转发到同一web容器下的url,中间传递的自己容器的request
-
请求重定向(Redirect) 实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的,状态码302
是客户端的操作,服务器返回目标地址给客户端,客户端去访问,那么客户端一开始的请求url就会发生变化。
过程:客户端发起http请求-->服务器接受http请求返回302状态码--->客户端看见是302状态码,重新发起http请求--->服务器处理请求给客户端响应
29. OSI五层
- 应用层-HTTP
- (表示层)
- (会话层)
- 传输层-TCP/UDP
- 网络层-IP
- 数据链路层
- 物理层
HTTP2
- 二进制传送。 使用的是二进制传送,二进制传送的单位是帧和流。帧组成了流,同时流还有流ID标示
- 多路复用。有流ID,所以通过同一个http请求实现多个http请求传输变成了可能,可以通过流ID来标示究竟是哪个流从而定位到是哪个http请求
- 头部压缩。 通过gzip和compress压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引Id就行,通过索引ID就可以知道表头的值了
- 服务器推送。 在客户端未经请求许可的情况下,主动向客户端推送内容
简述http1与http2的特点与各自的优劣
http1:
文本传输
长连接(HTTP/1.1)
管道机制:同一个TCP连接中,客户端可以同时发送多个请求
缺点:1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着
优点:采用文本传输,实现简单
http2:
二进制传输: 和http1传输文本不同,http2使用的是二进制传送,二进制传送的单位是帧和流,帧组成了流,流用流ID标示,二进制协议解析更高效,错误更少
多路复用: 多个http请求使用可以使用同一个http连接,通过流ID来标示究竟是哪个流从而定位到是哪个http请求,这样就不用每次请求都去建立http连接,提高了请求的效率,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应
头部压缩:减少每次请求的请求头大小,提升了性能,节约开销
服务器推送: 支持在客户端未经请求许可的情况下,主动向客户端推送内容,可以不需要客户端请求就将资源发送给客户端,减少请求次数,节约时间
缺点:改动http应用层,而TCP传输层已经被广泛应用,改动代价太大
HTTPS
设计模式
所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式使人们可以更加简单方便的复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路
创建型模式(5种)
1. 单例模式
懒汉式
public class Singleton {
private static Singleton instance;
//构造函数私有
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
public class HungrySingleton {
private static final HungrySingleton mHungrySingleton = new HungrySingleton();
private HungrySingleton() {
System.out.println("Singleton is create");
}
public static HungrySingleton getHungrySingleton() {
return mHungrySingleton;
}
}
DCL
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 第一个if (instance == null),只有instance为null的时候,才进入synchronized 第二个if
(instance == null),是为了防止可能出现多个实例的情况 - volatile: 主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
1. 给 singleton 分配内存
2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错
静态内部类
public class Singleton {
private Singleton (){
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
枚举类
public enum EnumSingleton {
//定义一个枚举的元素,它就是Singleton的一个实例
INSTANCE;
public void doSomething() {
}
}
2. 工厂方法模式
3. 抽象工厂模式
4. 建造者模式
结构型模式(7种)
1. 适配器模式
3. 装饰者模式
Java IO流的设计
行为型模式 ( 11种 )
4. 观察者模式
7. 解释器模式
9. 策略模式
Spring
1. Spring两大特性
- IOC控制反转
- AOP
2. Spring是如何启动的?
- 加载配置文件,容器在初始化时,contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文WebApplicationContext,再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这里是DispatcherServlet,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求
3. Spring七大模块
- 核心容器(Spring core)
核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。BeanFactory使用依赖注入的方式提供给组件依赖 - Spring上下文(Spring context)
Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能 - Spring面向切面编程(Spring AOP)
- Spring DAO模块
DAO模式主要目的是将持久层相关问题与一般的的业务规则和工作流隔离开来。Spring 中的DAO提供一致的方式访问数据库,不管采用何种持久化技术,Spring都提供一直的编程模型。Spring还对不同的持久层技术提供一致的DAO方式的异常层次结构 - Spring ORM模块
Spring 与所有的主要的ORM映射框架都集成的很好,包括Hibernate、JDO实现、TopLink和IBatis SQL Map等。Spring为所有的这些框架提供了模板之类的辅助类,达成了一致的编程风格 - Spring Web模块
Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。Web层使用Web层框架,可选的,可以是Spring自己的MVC框架,或者提供的Web框架,如Struts、Webwork、tapestry和jsf - Spring MVC框架(Spring WebMVC)
MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。Spring的MVC框架提供清晰的角色划分:控制器、验证器、命令对象、表单对象和模型对象、分发器、处理器映射和视图解析器。Spring支持多种视图技术
4. Spring三大组件
-
Bean组件
组件作用:Bean组件在Spring中的Beans包下,为了解决三件事。Bean的创建,Bean的定义,Bean的解析。最关心的就是Bean的创建
①Bean的创建:
工厂模式的实现,顶层接口是:BeanFactory
虽然最终实现类是DefaultListableBeanFactory,但是其上层接口都是为了区分在Spring内部对象的传递和转换的过程,对对象的数据访问所做的限制。
ListableBeanFactory:可列表的
HierarchicalBeanFactory:可继承的
AutowriteCapableBeanFactory:可自动装配的
这四个接口,共同定义了Bean的集合,Bean之间的关系,Bean的行为
②Bean的定义
Bean的定义完整的描述在Spring配置文件中节点中,包括子节点等
在Spring内部它被转换成BeanDefinition对象,后续操作都是对这个对象操作
主要是BeanDefinition来描述
③Bean的解析
BeanDefinitionReader
Bean的解析就是对Spring配置文件以及对Tag的解析 -
Context组件
组件作用: 在Spring中的context包下,为Spring提供运行环境,用以保存各个对象状态
Context作为Spring的IOC容器,整合了大部分功能或说大部分功能的基础,完成了以下几件事:
1、标识一个应用环境
2、利用BeanFactory创建Bean对象
3、保存对象关系表
4、能够捕获各种事件
ApplicationContext是context的顶级父类,除了能标识一个应用的基本信息外,还继承了五个接口,扩展了Context的功能。并且继承了BeanFactory,说明Spring容器中运行的主体对象是Bean,另外还继承了ResourceLoader,可以让ApplicationContext可以访问任何外部资源 -
Core组件
访问资源
1、它包含了很多关键类,一个重要的组成部分就是定义的资源的访问方式,这种把所有资源都抽象成了一个接口的方式很值得学习。
2、Resource接口封装了各种可能的资源类型,继承了InputStreamSource接口。
加载资源的问题,也就是资源加载者的统一,由ResourceLoader接口来完成。
默认实现是:DefaultResourceLoader
5. spring 的 controller 是单例还是多例,怎么保证并发的安全
-
Spring 多线程请求过来调用的Controller对象都是一个,而不是一个请求过来就创建一个Controller对象
-
@Scope(“prototype”),这样每次请求调用的类都是重新生成的(每次生成会影响效率)
使用ThreadLocal来保存类变量
6. Spring 框架中都用到了哪些设计模式?
- 代理模式—在AOP和remoting中被用的比较多
- 单例模式—在spring配置文件中定义的bean默认为单例模式
- 模板方法—用来解决代码重复的问题。eg:RestTemplate、 JmsTemplate、JpaTemplate
- 工厂模式—BeanFactory用来创建对象的实例
7. ApplicationContext Bean生命周期
- 1. 对scope为singleton且非懒加载的bean进行实例化,按照配置信息设置属性
- 2. 如果Bean实现了Aware接口,注入BeanID、BeanFactory、ApplicationContext(在Bean中操作容器,例如得到实例名)
- 3. 如果实现BeanPostProcessor,执行则会回调该接口的postProcessBeforeInitialzation前置方法(在实例化完成后,对Bean添加自定义处理逻辑)
- 4. 如果实现InitializingBean接口,执行afterPropertiesSet()方法(实现接口执行方法,做一些属性自定义的事)
- 5. 如果Bean配置了init-method方法,则会执行init-method配置的方法(调用Bean自身的初始化方法)
- 6. 执行如果实现BeanPostProcessor接口的postProcessAfterInitialization()后置方法(Bean实例化之后的工作)
- 7. 初始化完成
- 8. 如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法
- 9. 如果Bean配置了destroy-method属性,则会执行destroy-method配置的方法,至此,整个Bean的生命周期结束
8. Spring的BeanFactory和ApplicationContext的区别?
- ApplicationContext是实现类,继承ListableBeanFactory(继承BeanFactory),功能更多
ApplicationContext默认立即加载,BeanFactory延迟加载 - beanFactory主要是面对与 spring 框架的基础设施,面对 spring 自己。而 Applicationcontex 主要面对与 spring 使用的开发者
9. Spring IOC 怎么注入类,怎么实例化对象
实例化
- Spring IoC容器需要根据Bean定义里的配置元数据使用反射机制来创建Bean
- 使用构造器实例化Bean 有参/无参;使用静态工厂实例化Bean;使用实例工厂实例化Bean
- 使用@Autowire注解注入的时机则是容器刚启动的时候就开始注入;注入之前要先初始化bean;ApplicationContext 的初始化和BeanFactory 有一个重大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean 时才实例化目标Bean;而ApplicationContext 则在初始化应用上下文时就实例化所有单实例的Bean
10. 谈谈Spring MVC的工作原理是怎样的?
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet接收到客户端请求,找到对应的处理器映射器HandlerMapping,根据映射规则,找到对应的处理器(Handler)
- DispatcherServlet调用HandlerAdapter处理器适配器执行Handler的处理方法,执行完成会返回一个ModelAndView
- DispatcherServlet根据得到的ModelAndView中的视图对象,找到一个合适的ViewResolver(视图解析器),根据视图解析器的配置,DispatcherServlet将要显示的数据传给对应的视图,最后显示给用户
11. Spring注入
- 接口注入、setter注入、构造器注入
12. Spring bean 范围
- singleton 单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
- prototype IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例
- request 该属性仅对HTTP请求产生作用,每次HTTP请求都将产生一个新实例。只有在Web应用中使用Spring时,该作用域才有效
- session 同一个Session共享一个Bean实例。不同Session使用不同的实例
- global session 所有的Session共享一个Bean实例
13. 谈谈Spring中自动装配的方式有哪些?
- no: 不进行自动装配,手动设置Bean的依赖关系
- byName: 根据Bean的名字进行自动装配
- byType: 根据Bean的类型进行自动装配
- constructor: 类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误
- autodetect: 如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配【在Spring3.0以后的版本被废弃,已经不再合法了】
14. aop的应用场景?
- 日志
- 数据源切换
- 事务
15. AOP的原理是什么?
- AspectJ:静态代理,即用一种AspectJ支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,相对于下面说的运行时增强,编译时增强的性能更好
- Spring AOP:动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持
16. JDK动态代理只能为接口创建动态代理实例,而不能对类创建动态代理。
- 需要获得被代理目标类的接口信息(应用Java的反射技术),生成一个实现了代理接口的动态代理类(字节码),再通过反射机制获得动态代理类的构造函数,利用构造函数生成动态代理类的实例对象,在调用具体方法前调用InvokeHandler方法来处理
17. 你如何理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、织入(Weaving)、切面(Aspect)这些概念?
- 连接点(Joinpoint):可以作为被切入点的机会,所有方法都可以作为切入点。Spring仅支持方法的连接点 eg:hi();
- 切点(Pointcut):让连接点能被spring框架找到并且,正则表达式
- 增强(Advice):类里的方法以及这个方法如何织入到目标方法的方式(注解 + 方法),Aspect是类,Advice是类里的方法
- 织入(Weaving):Aop的实现过程
- 切面(Aspect):通用功能的代码实现,切面类
18. Spring支持的事务管理类型有哪些?你在项目中使用哪种方式?
- 编程式事务: 在代码中显式调用开启事务、提交事务、回滚事务的相关方法,基于
<tx>
和<aop>
命名空间的声明式事务管理是目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活 - 声明式事务: 底层是建立在 AOP 的基础之上。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务,基于 @Transactional 的方式将声明式事务管理简化到了极致
19. Servlet的生命周期
= 分为init,service,destory三个流程,当第一个请求/服务器启动时init,然后会给所有进来的请求分配一个线程,执行doGet\doPost方法,最后执行destory,被GC回收
MyBatis
1. #{}和${}的区别是什么?
#{}
是预编译处理,提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译,mybatis在处理#{}
时,会将sql中的#{}
替换为?
号,调用PreparedStatement的set方法来赋值,最后注入进去是带引号的,${}
是字符串替换,mybatis在处理${}
时,就是把${}
替换成变量的值- 使用
#{}
可以有效的防止SQL注入,提高系统安全性
2. 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
1.通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致
<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>
2.通过<resultMap>
来映射字段名和实体类属性名的一一对应的关系
<select id="getOrder" parameterType="int" resultMap="orderresultmap">
select * from orders where order_id=#{id}
</select>
<resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
<!–用id属性来映射主键字段–>
<id property=”id” column=”order_id”>
<!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–>
<result property = “orderno” column =”order_no”/>
<result property=”price” column=”order_price” />
</reslutMap>
3. 如何获取自动生成的(主)键值?
通过LAST_INSERT_ID()获取刚插入记录的自增主键值,在insert语句执行后,执行select LAST_INSERT_ID()就可以获取自增主键
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User">
<selectKey keyProperty="id" order="AFTER" resultType="int">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO USER(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})
</insert>
4. 在mapper中如何传递多个参数?
1.使用占位符的思想
#{0}
,#{1}
方式
//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser"resultMap="BaseResultMap">
select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}
</select>
@param
注解方式
public interface usermapper {
user selectuser(@param(“username”) string username, @param(“hashedpassword”) string hashedpassword);
}
<select id=”selectuser” resulttype=”user”>
select id, username, hashedpassword
from some_table
where username = #{username}
and hashedpassword = #{hashedpassword}
</select>
2.使用Map集合作为参数来装载
<select id=”selectuser” resulttype=”user”>
select id, username, hashedpassword
from some_table
where username = #{username}
and hashedpassword = #{hashedpassword}
</select>
<!--分页查询-->
<select id="pagination" parameterType="map" resultMap="studentMap">
/*根据key自动找到对应Map集合的value*/
select * from students limit #{start},#{end};
</select>
5. mybatis缓存机制
mybatis提供了缓存机制减轻数据库压力,提高数据库性能
缓存分为两级:一级缓存、二级缓存
-
一级缓存:
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存
mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问 -
二级缓存:
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率
二级缓存是mapper级别的缓存,也就是同一个namespace的mappe.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域
二级缓存默认是没有开启的。需要在setting全局参数中配置开启二级缓存conf.xml:
<settings> <setting name="cacheEnabled" value="true"/>默认是false:关闭二级缓存 <settings>
在userMapper.xml中配置:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>当前mapper下所有语句开启二级缓存
若想禁用当前select语句的二级缓存,添加useCache="false"修改如下:
<select id="getCountByName" parameterType="java.util.Map" resultType="INTEGER" statementType="CALLABLE" useCache="false">
6. Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?
- Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能
- Mybatis提供了9种动态sql标签:
trim
、where
、set
、foreach
、if
、choose
、when
、otherwise
、bind
- 其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能
7. Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
- 如果配置了namespace是可以重复的,因为Statement实际上就是namespace + id
- 如果没有配置namespace的话,那么相同的id就会导致覆盖了
8. 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
- Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的
- 而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具
9. 通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
-
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数
-
Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement
com.mybatis3.mappers.StudentDao.findStudentById 可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。
-
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略
-
Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为接口生成代理对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回
10. 接口绑定有几种实现方式,分别是怎么实现的?
- 一种是通过注解绑定,就是在接口的方法上面加上@Select@Update等注解里面包含Sql语句来绑定
- 另外一种就是通过xml里面写SQL来绑定,在这种情况下,要指定 xml映射文件里面的namespace必须为接口的全路径名
11. Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数
select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10
12. Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
- Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false
- 它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
- 当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的
13. MyBatis与Hibernate有哪些不同?
-
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
-
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大
-
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的缺点是学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
JVM
JVM内存划分
1. 方法区:
- 又叫永久代,方法区与Java堆一样,也是线程共享的并且不需要连续的内存,其用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用. 字面量比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值. 符号引用:包括以下三类常量:类和接口的全限定名、字段的名称和描述符和方法的名称和描述符
- JDK1.8撤销方法区,增加元空间
- 线程共享
2. 堆:
- Java 堆的唯一目的就是存放对象实例,几乎所有的对象实例(和数组)都在这里分配内存
- Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。而且,Java堆在实现时,既可以是固定大小的,也可以是可拓展的,并且主流虚拟机都是按可扩展来实现的(通过-Xmx(最大堆容量)和 -Xms(最小堆容量)控制)。如果在堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出 OutOfMemoryError 异常
- TLAB (线程私有分配缓冲区) : 虚拟机为新生对象分配内存时,需要考虑修改指针 (该指针用于划分内存使用空间和空闲空间)时的线程安全问题,因为存在可能出现正在给对象A分配内存,指针还未修改,对象B又同时使用原来的指针分配内存的情况。TLAB的存在就是为了解决这个问题:每个线程在Java堆中预先分配一小块内存
TLAB,哪个线程需要分配内存就在自己的TLAB上进行分配,若TLAB用完并分配新的TLAB时,再加同步锁定,这样就大大提升了对象内存分配的效率 - 线程共享
3. 程序计数器:
- 在多线程情况下,当线程数超过CPU数量或CPU内核数量时,线程之间就要根据时间片轮询抢夺CPU时间资源。也就是说,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器去记录其正在执行的字节码指令地址
- 线程私有
4. 虚拟机栈:
- 每个方法从调用直至完成的过程,对应一个栈帧在虚拟机栈中入栈到出栈的过程
- 线程私有
5. 本地方法栈:
- 本地方法栈与Java虚拟机栈非常相似,也是线程私有的,区别是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机执行Native 方法服务。与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError异常
- 线程私有
GC 算法
1. 引用计数法
- 循环引用
2. 可达性分析算法
- 通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用
3. 标记清除算法
- 标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收
- 效率问题:标记和清除两个过程的效率都不高
- 空间问题:标记-清除算法不需要进行对象的移动,并且仅对不存活的对象进行处理,因此标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
4. 复制算法
- 复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效
- 实践中会将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用内存空间为整个新生代容量的90% ( 80%+10% ),只有10% 的内存会被“浪费”
5. 标记整理算法
- 标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代)
- 无内存碎片
垃圾收集器
1. CMS
- 一种以获取最短回收停顿时间为目标的收集器
- 老年代收集器
- 标记-清除算法
- 初始标记
- 并发标记
- 重新标记
- 并发清除
- 整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的
缺点:
-
CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低
-
CMS收集器无法处理浮动垃圾,可能会出现并发模式故障失败而导致Full GC产生
- 浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现的标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”
-
CMS是一款“标记-清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC
2. G1
- 面向服务端的垃圾收集器
- 当今收集器技术发展的最前沿成果之一
- 标记-整理+复制算法
- 横跨整个堆内存: G1回收的范围是整个Java堆(包括新生代,老年代),将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分Region(不需要连续)的集合
- 可预测的停顿: G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
- 避免全堆扫描——Remembered Set: 为了避免全堆扫描的发生,虚拟机为G1中每个Region维护了一个与之对应的Remembered Set。虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏
- 初始标记
- 并发标记,从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行
- 最终标记
- 筛选回收,首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率
JVM相关知识点
1. 方法区的回收
- 主要是针对 常量池的回收(判断引用)和对类型的卸载
- 回收类:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例加载
- 该类的ClassLoader已经被回收
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
2. 类加载过程
- 加载:获取二进制字节流、存储结构转化为运行时数据结构、生成Class对象
- 验证:确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 准备:为类变量分配内存并设置类变量初始值,仅包括类变量
- 解析:将虚拟机常量池内的符号引用替换为直接引用
- 初始化:执行类中定义的Java程序代码
3. 类变量初值
- 布尔型(boolean)变量: false
- byte、short、int、long: 0
- 字符型:’\u0000’(空字符)
- 浮点型(float double): 0.0
- 引用类型(String): null
4. OOM
- Java堆溢出
内存泄漏
内存溢出 - 虚拟机栈和本地方法栈溢出
建立过多线程 - 方法区和运行时常量池溢出
存放太多Class信息、常量 - 本机直接内存溢出
5. 堆溢出和栈溢出
- 堆溢出:不断创建对象
- 栈溢出:死循环或者是递归太深
6. 什么时候会发生FullGC
- System.gc()
- 老年代空间不足
- 永久代空间不足
- Minor GC前判断晋升到老年代的对象平均大小大于老年代剩余空间
- CMS GC时出现promotion failed(担保:s1s2–old)和concurrent mode failure
- 堆中分配很大的对象
7. JVM调优参数
-
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xss:每个线程虚拟机栈的大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小 -
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器 -
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename -
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) -
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用於单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数
8. 为什么分代收集
不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率
9. 新生代进入老生代的情况
- 对象优先在Eden分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC。现在的商业虚拟机一般都采用复制算法来回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。 当进行垃圾回收时,将Eden和Survivor中还存活的对象一次性地复制到另外一块Survivor空间上,最后处理掉Eden和刚才的Survivor空间。(HotSpot虚拟机默认Eden和Survivor的大小比例是8:1)当Survivor空间不够用时,需要依赖老年代进行分配担保
- 大对象直接进入老年代。所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组
- 长期存活的对象(-XX:MaxTenuringThreshold)将进入老年代。当对象在新生代中经历过一定次数(默认为15)的Minor GC后,就会被晋升到老年代中
- 动态对象年龄判定。为了更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄
10. 空间分配担保
- 如果老年代的剩余空间 < 之前转入老年代的对象的平均大小,则触发Full GC
- 如果老年代的剩余空间 > 之前转入老年代的对象的平均大小,并且允许担保失败,则直接Minor GC,不需要做Full GC
- 如果老年代的剩余空间 > 之前转入老年代的对象的平均大小,并且不允许担保失败,则触发Full GC
11. 类加载器
- 启动类加载器
- 扩展类加载器
- 应用程序类加载器
12. JAVA虚拟机的作用?
- 将java字节码解释为具体平台的具体指令,做到跨平台,也进行Java对象生命周期的管理,实现内存管理
13. JAVA虚拟机中,哪些可作为ROOT对象?
- 虚拟机栈中的引用对象
- 本地方法栈中的引用对象
- 方法区中类静态变量引用的对象
- 方法区中常量引用的对象
14. minor gc如果运行的很慢,可能是什么原因引起的?
- 新生代太大,导致gc时有太多对象需要回收
服务器
1. 反向代理是什么?
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。客户端只会得知反向代理的IP地址,而不知道在代理服务器后面的服务器簇的存在
2. 负载均衡是什么?
负载均衡(Load balancing)是一种计算机技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。 使用带有负载均衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。主要作用是将大量作业合理地分摊到多个操作单元上进行执行,用于解决互联网架构中的高并发和高可用的问题
Linux
1. 常用命令
-
查找文件
find path -name "name" - find ~ -name "target*" 模糊查找 - find ~ -iname "target*" 忽略大小写
-
查找包含某个字段的文件
grep "文件内容" 文件名
|
:管道操作符,将前一个命令的正确输出作为下一个命令的输入
ps
:查看进程
grep
:搜索过滤
ps -ef | grep tomcat | grep -v "grep"
grep -v
:过滤掉相关内容字符串
top
:显示进程状态
free
:查看内存
df
:查看linux系统磁盘空间
du
:显示每个文件和目录的磁盘使用空间
tail
:查看文件尾部内容
cat
:显示整个文件、创建文件、合并文件
算法
1. 红黑树
- 旋转和颜色变化规则:所有插入的点默认为红色
- 变颜色的情况:当前节点的父亲是红色,且它的祖父节点的另一个节点也是红色(叔叔节点)
(1)把父节点设为黑色
(2)把叔叔节点也设为黑色
(3)把祖父也就是父亲的父亲设为红色(爷爷)
(4)把指针定义到祖父节点设为当前要操作的节点 - 左旋:当前父节点是红色,叔叔是黑色的时候,且当前的结点是右子树。左旋以父节点作为左旋
- 当前父节点是红色,叔叔是黑色的时候,且当前的节点是左子树
(1)把父节点变为黑色
(2)把祖父节点变为红色(爷爷)
(3)以祖父节点旋转
- 变颜色的情况:当前节点的父亲是红色,且它的祖父节点的另一个节点也是红色(叔叔节点)
2. 不能加密消息的算法
MD5是生成消息摘要的加密算法,因为其无秘钥,因此不用于文本加密,主要用于数字签名等用途,而其他算法则均为典型的文本加密算法
3. 开放地址法和链地址法
- 开放地址法:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止
- 链地址法:如果哈希表空间为 0 ~ m - 1 ,设置一个由 m 个指针分量组成的一维数组 ST[ m ], 凡哈希地址为 i 的数据元素都插入到头指针为 ST[ i ] 的链表中,类似HashMap
4. 线性表
逻辑结构上相邻的数据在实际的物理存储中有两种形式:分散存储和集中存储
- 数据元素在内存中集中存储,采用顺序表示结构,简称“顺序存储”
- 数据元素在内存中分散存储,采用链式表示结构,简称“链式存储”
5. 给你一个未知长度的链表,怎么找到中间的那个节点?
快慢指针,快指针走两步,慢指针走一步,快指针到尾了,慢指针走到一半
6. 用Java实现一个二分查找
public static void main(String[] args) {
int srcArray[] = {3,5,11,17,21,23,28,30,32,50,64,78,81,95,101};
System.out.println("下标为-1表示未找到!");
System.out.println("下标:"+ binSearch(srcArray, 0, srcArray.length - 1, 81));
System.out.println("下标:"+ binSearch(srcArray, 100));
}
// 二分查找递归实现
public static int binSearch(int srcArray[], int start, int end, int key) {
int mid = (end - start) / 2 + start;
if (srcArray[mid] == key) {
return mid;
}
if (start >= end) {
return -1;
} else if (key > srcArray[mid]) {
return binSearch(srcArray, mid + 1, end, key);
} else if (key < srcArray[mid]) {
return binSearch(srcArray, start, mid - 1, key);
}
return -1;
}
// 二分查找普通循环实现
public static int binSearch(int srcArray[], int key) {
int mid = srcArray.length / 2;
if (key == srcArray[mid]) {
return mid;
}
int start = 0;
int end = srcArray.length - 1;
while (start <= end) {
mid = (end - start) / 2 + start;
if (key < srcArray[mid]) {
end = mid - 1;
} else if (key > srcArray[mid]) {
start = mid + 1;
} else {
return mid;
}
}
return -1;
}
操作系统
1. 进程间通讯方式
-
共享内存
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。- 低级方式:基于数据结构的共享
- 高级方式:基于存储区的共享
-
消息队列
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点- 直接通信方式:直接把消息挂到接收进程的消息队列
- 间接通信方式:挂到某个中间实体,接收进程找实体接收消息,类似电子邮件
-
管道
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系- 利用一种特殊的pipe文件连接两个进程
- 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信
-
信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 -
socket(套接字)
套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信
文件
2. 线程通信方式
锁机制
信号量机制
信号机制
3. 线程同步方式
- 临界区:通过对多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问
- 互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会同时被多个线程访问
- 信号量:它允许多个线程同一时刻访问同一资源,但是需要限制同一时刻访问此资源的最大线程数目。信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中PV操作相似
- 事件(信号):通过通知操作的方式来保持多线程的同步,还可以方便的实现多线程的优先级比较的操作
4. 调度
- 作业调度(高级调度):选择处于后备状态的作业分配资源,发送频率低;调度对象是作业,主要用于多道批处理系统中,而在分时和实时系统中不设置高级调度
- 内存调度(中级调度):选择暂时不能允许的进程调出内存,发送频率中等;调度对象是进程,在多道批处理,分时和实时三种类型的OS中,都必须配置这级调度
- 进程调度(低级调度):选择就绪队列中合适的进程分配处理机,发生频率高;内存调度,引入中级调度的主要目的是,提高内存利用率和系统吞吐量,中级调度实际上就是存储器管理中的对换功能
5. 进程组成部分
程序、数据集合、进程控制块(PCB)
- 进程控制块是进程存在的唯一标识
6. 进程调度方式
- 剥夺式:有更为重要或紧迫的进程需要使用处理机,立即分配
- 非剥夺式:有更为重要或紧迫的进程需要使用处理机,仍让当前进程继续执行
7. 进程调度算法法
- 先来先服务调度算法(FCFS):选择最先进入队列的;既可以作为作业调度算法也可以作为进程调度算法;按作业或者进程到达的先后顺序依次调度;因此对于长作业比较有利;
- 短作业优先调度算法(SJF):作业调度算法,算法从就绪队列中选择估计时间最短的作业进行处理,直到得出结果或者无法继续执行;缺点:不利于长作业;未考虑作业的重要性;运行时间是预估的,并不靠谱
- 优先级调度算法(PSA):基于作业的紧迫程度,由外部赋予作业相应的优先级,调度算法是根据该优先级进行调度的
- 高相应比优先调度算法:选择响应比最高的 响应比Rp = (等待时间+要求服务时间) / 要求服务时间
- 时间片轮转调度:总数选择就绪队列中的第一个进程,但仅能运行一个时间片
- 多级反馈队列调度算法:目前公认较好的调度算法;设置多个就绪队列并为每个队列设置不同的优先级,第一个队列优先级最高,其余依次递减。优先级越高的队列分配的时间片越短,进程到达之后按FCFS放入第一个队列,如果调度执行后没有完成,那么放到第二个队列尾部等待调度,如果第二次调度仍然没有完成,放入第三队列尾部…。只有当前一个队列为空的时候才会去调度下一个队列的进程
8. 虚拟内存
如果存在一个程序,所需内存空间超过了计算机可以提供的实际内存,那么由于该程序无法装入内存所以也就无法运行。单纯的增加物理内存只能解决一部分问题,但是仍然会出现无法装入单个或者无法同时装入多个程序的问题。但是可以从逻辑的角度扩充内存容量,即可解决上述两种问题。
基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其余部分留在外存,就可以启动程序执行。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。这样,系统好像为用户提供了一个比实际内存大得多的存储器,称为虚拟存储器。
虚拟存储器的特征:
- 多次性:一个作业可以分多次被调入内存。多次性是虚拟存储特有的属性
- 对换性:作业运行过程中存在换进换出的过程(换出暂时不用的数据换入需要的数据)
- 虚拟性:虚拟性体现在其从逻辑上扩充了内存的容量(可以运行实际内存需求比物理内存大的应用程序)。虚拟性是虚拟存储器的最重要特征也是其最终目标。虚拟性建立在多次性和对换性的基础上行,多次性和对换性又建立在离散分配的基础上
虚拟存储器的实现方法:分页请求系统,请求分段系统
9. 页面置换算法
- 最佳(Optimal)置换算法:只具有理论意义的算法,用来评价其他页面置换算法。置换策略是将当前页面中在未来最长时间内不会被访问的页置换出去。
- 先进先出(FIFO)置换算法:简单粗暴的一种置换算法,没有考虑页面访问频率信息。每次淘汰最早调入的页面。
- 最近最久未使用LRU(Least Recently Used)置换算法:算法赋予每个页面一个访问字段,用来记录上次页面被访问到现在所经历的时间t,每次置换的时候把t值最大的页面置换出去(实现方面可以采用寄存器或者栈的方式实现)。
- 最少使用LFU(Least Frequently Used)置换算法:设置寄存器记录页面被访问次数,每次置换的时候置换当前访问次数最少的。
- 改进型Clock算法:在Clock算法的基础上添加一个修改位,替换时根究访问位和修改位综合判断。优先替换访问位和修改位都是0的页面,其次是访问位为0修改位为1的页面。
- 页面缓冲算法(Page Buffering Algorithm,PBA)
死锁
产生原因:非剥夺资源的竞争和进程的不恰当推进顺序
定义:多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进
解决方案
预防死锁
- 破坏互斥条件
- 破坏不可剥夺条件
- 破坏请求和保持条件
- 破坏循环等待条件
避免死锁
银行家算法:采用预分配策略检查分配完成时系统是否处于安全状态
- 安全状态:能找到一个分配资源的序列能让所有进程都顺利完成
**检测死锁:**利用死锁定理化简资源分配图以检测死锁的存在
解除死锁:
- 资源剥夺法:挂起某些死锁进程并抢夺它的资源,以便让其他进程继续推进
- 撤销进程法:强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源
- 进程回退法:让进程回退到足以回避死锁的地步
UML
UML中有哪些常用的图?
用例图、时序图、活动图、状态图