面试知识
Java知识点
- 重写和重载
- StringBuffer & StringBuilder
- 反射
- 泛型
- 异常
- 乐观锁 & 悲观锁
- 多线程
- 序列化
- Collections
- cookie & session
- 垃圾回收过程
- 数据库隔离级别
- 线程池
- static
- MVCC
- TCP
重写和重载
1.重写(Override)
从字面上看,重写就是 重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Son s = new Son();
s.sayHello();
}
public void sayHello() {
System.out.println("Hello");
}
}
class Son extends Father{
@Override
public void sayHello() {
// TODO Auto-generated method stub
System.out.println("hello by ");
}
}
重写 总结: 1.发生在父类与子类之间 2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同 3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private) 4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
——————————————————————————————————————————
2.重载(Overload) 在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
public class Father {
public static void main(String[] args) {
// TODO Auto-generated method stub
Father s = new Father();
s.sayHello();
s.sayHello("wintershii");
}
public void sayHello() {
System.out.println("Hello");
}
public void sayHello(String name) {
System.out.println("Hello" + " " + name);
}
}
重载 总结: 1.重载Overload是一个类中多态性的一种表现 2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序) 3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准
StringBuffer & StringBuilder
StringBuffer代表可变字符串对象,对象是一个字符序列可变的字符串,它没有重新生成一个对象,而且在原来的对象中可以连接新的字符串。
public class stringBuffer {
public static void main(String[] args) {
// stringBuffer 和 stringBuilder 操作类似
StringBuffer stringBuffer = new StringBuffer("aa");
System.out.println(stringBuffer);
// 追加字符串
stringBuffer.append("bb");
System.out.println(stringBuffer);
// 在指定位置插入字符串
stringBuffer.insert(1, "ff");
System.out.println(stringBuffer);
// 获取指定位置的字符
System.out.println(stringBuffer.charAt(4));
// 反向
stringBuffer.reverse();
System.out.println(stringBuffer);
// 变更指定位置的字符
stringBuffer.setCharAt(0, 'c');
System.out.println(stringBuffer);
// 变更字符串长度
stringBuffer.setLength(2);
System.out.println(stringBuffer);
}
}
StringBuffer类中的方法都添加了synchronized关键字,也就是给这个方法添加了一个锁,用来保证线程安全
反射
反射的定义
Java反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取类信息以及动态调用对象内容就称为Java语言的反射机制。
反射的实现
要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个Class对象就保存了这个类的一切信息。反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。
获取Class对象的三种方式
Q&A
反射原理,反射为什么可以通过字符串获取一个类
泛型
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
Java语言引入泛型的好处是安全简单。可以将运行时错误提前到编译时错误。
public class ArrayList<T> {
private T[] array;
private int size;
public void add(T e) {...}
public void remove(int index) {...}
public T get(int index) {...}
}
T 可以是任何class。这样一来就实现了:编写一次模板,可以创建任意类型的ArrayList :
ArrayList<String> stringArrayList = new ArrayList<String>();
ArrayList<Integer> integerArrayList = new ArrayList<Integer>();
ArrayList<Float> floatArrayList = new ArrayList<Float>();
ArrayList<Person> personArrayList = new ArrayList<Person>();
异常
乐观锁 & 悲观锁
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下,在此期间有没有别人去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS。
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。在Java中,synchronized从偏向锁、轻量级锁到重量级锁,全是悲观锁。JDK提供的Lock实现类全是悲观锁。
乐观锁 版本号
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
乐观锁 CAS
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
这样说或许有些抽象,我们来看一个例子:
1.在内存地址V当中,存储着值为10的变量。
2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。
3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。
4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。
6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。
7.线程1进行SWAP,把地址V的值替换为B,也就是12。
- 例如启动两个线程,每个线程中让静态变量count循环累加100次。
public class TestThreadAtomic {
private static Integer count = 0;
public static void main(String[] args) {
for(int i = 0 ; i < 2 ; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count++;
}
}
}).start();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(count);
}
}
// 结果不是200
加了同步锁之后,count自增的操作变成了原子性操作,所以最终的输出一定是count=200,代码实现了线程安全。
public class TestThreadAtomic {
private static Integer count = 0;
public static void main(String[] args) {
for(int i = 0 ; i < 2 ; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
synchronized (count.getClass()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count++;
}
}
}
}).start();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(count);
}
}
// 结果是200
Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。
所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。
现在我们尝试在代码中引入AtomicInteger类:
import java.util.concurrent.atomic.AtomicInteger;
public class TestThreadAtomic {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count.addAndGet(1);
}
}
}).start();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(count);
}
}
// 结果为200
使用AtomicInteger之后,最终的输出结果同样可以保证是200。并且在某些情况下,代码的性能会比Synchronized更好。
CAS的缺点:
- CPU开销较大。在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
- 不能保证代码块的原子性。CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
- ABA问题。这是CAS机制最大的问题所在。
Q&A
JDK哪里用到了乐观锁? AQS