1. HashMap 的源码、实现原理、JDK8中对HashMap 的优化。
2. HashMap的扩容机制。
3. HashMap、HashTable、ConcurrentHashMap 的区别。
4. 极高并发下HashTable 和 ConcurrentHashMap 的性能比较及理由。
5. ArrayList 和 LinkedList 的区别、数组、链表和双向链表的了解。
6. ArrayList在循环过程中删除元素会不会出问题?为什么?
7. HashMap 和 TreeMap 的区别。
8. 谈谈你对并发容器的理解。
作用域 当前类 同一个package 子孙类 其他package
public 1 1 1 1
protected 1 1 1 0
friendly 1 1 0 0
private 1 0 0 0
构造方法只能呗重载,不能被重写,不能被继承
(1)== 比较的是两个对象内存的引用(即内存地址)是否相等
(2)equals判断的只是对象的值是否相等
(3)成对重写,重写hashcode,必须重写equals。并且需要保持一致性,hashcode的值相等,equals必须相等,反之亦然。否则使用hashmap,可能会效率低下。
(1)string是不可变性,追加的时候会产生很多中间对象
(2)StringBuffer:可变,追加的时候不会产生中间对象,但是线程安全,开销大
(3)StringBuilder:可变,追加的时候不会产生中间对象,但是线程不安全,开销小
一般来说,操作字符串不需要线程安全,所以使用StringBuilder即可。解析http字符串需要线程安全。
(1)hashcode、equals、toString、wait、notify、notifyAll、clone、registerNatives、getClass、finalize
(1)方式:实现java.io.Serializable接口或者实现Externlizable接口,后者继承前者,并新增了两个方法,可以定义哪些属性序列化,哪些不必序列化。
(2)原因:将对象转化成二进制,用于保存或者网络传输。
(1)jdk动态代理:通过实现接口的方式
(2)cglib动态代理:通过继承父类的方式
补充:动态代理和静态代理的区别:
静态代理:由程序员创建的代理类或者工具先生成源代码文件再对他进行编译。在程序运行前代理类的.class文件就已经存在了。
动态代理:在程序运行时运用反射机制动态创建而成。
(1)值传递: 在方法调用过程中,把实际的参数传给形参,相当于复制一份实参的值传递给形参,
如果形参的值改变了,原来的实参值不会改变。除非方法有返回值,覆盖了之前的值。
(2)引用传递:在方法调用的时候,实参传给形参,传的是实参的引用地址,所以形参的值改变了,
实参的值也改变了,因为二者指向同一个地址。
(3)java中是值传递,不管是基本类型还是引用类型都是按照值传递的。
引用类型的值传递,传递的是引用对象在堆中的地址的拷贝,所以如果形参改变,实参也会改变,因为二者指向同一个地址;
基本类型的值传递,传递的是面值的拷贝,二者是不同的,所以形参改变,实参并不改变。
都是OSI模型中的传输层
tcp:tcp传输较安全,提供可靠的通信传输
udp:传输不如tcp安全,但比tcp传输快
11. Java 中如何处理跨域访问。由于浏览器的同源策略(同源:指的是域名、协议、端口都相等)导致了跨域请求问题。
jsonp:将请求地址写到script标签里,因为浏览器同源策略不检测script的静态资源请求
Nginx代理
CORS
常用的会话技术是:session和cookied,记录用户从登录网址到浏览购买的一系列操作,session在服务器端记录,cookie在客户端记录。
session:(1)存储在服务器 (2)安全(3)可以存储一些登录信息
cookie:(1)存储在客户端(2)不安全(3)存储数据类型少,存储的东西不多
应用场景:登录一个网站之后,第二天自动登录了,这是由于cookie的原因,记住密码了。
购物车可以添加到session中,前后端都需要购物车的数量情况,需要保存。
恶意提交表单,刷新页面或者使用postman提交,提交重复数据,增加服务器的负载,严重可能会宕机
(1)使用js禁用button,不推荐(可以刷新页面绕过,或者使用postman);
(2)数据库字段(用户名、电话、邮箱等)增加唯一约束,服务器及时捕捉插入数据异常;
(3)在页面打开时,从后台传个subtoken存到隐藏域中,并且在session里面也保存该标记subToken,每次提交表单把这个subToken带上,
如果没有或者重复就不通过,否则处理该数据,处理完毕清空session
三种:继承Thread、实现Callable、实现Runnable接口
通过f.get()方法,可以获取线程的返回值,但是如何线程还没执行完,会一直阻塞,直到得到返回结果
public static void main(String [] args){
CallableThread callableThread = new CallableThread();
for(int i = 0;i<10;i++){
FutureTask f = new FutureTask<Callable>(callableThread);
new Thread(f).start();
try {
Integer a = (Integer) f.get();
//获取子线程返回的值
System.out.println("子线程返回值a:"+a);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class CallableThread implements Callable{
int i = 1;
@Override
public Object call() throws Exception {
System.out.println("this is callable"+Thread.currentThread().getName());
return i++;
}
}
线程池有七个参数:
(1)corePollSize:核心线程数
核心线程会一直存活,即使没有任务执行。当线程数小于核心线程数,即使有空闲线程,也会优先创建新线程进行处理。
(2)maximumPoolSize:最大线程数。表明线程中最多能创建的线程数。
当前线程数 >= corePollSize ,且任务队列已满时,系统会创建新的线程去执行新任务
当前线程数 = maximumPoolSize ,且任务队列已满时,线程池会拒绝处理任务并抛异常
(3)keepAliveTime:空闲线程保存的时间。
空闲时间达到keepAliveTime时,线程会退出,直到线程数等于corePollSize
(4)TimeUnit:空闲线程保留的时间单位。
(5)BlockingQueue<Runnable>:阻塞队列(又叫任务队列容量),存储等待执行的任务。
(6)ThreadFactory:线程工厂,用于创建线程。
(7)RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常存储策略,任务拒绝处理器。
当线程数达到maximumPoolSize,且队列已满,会拒绝新任务。
当线程池调用shutdown,和真正调用shutdown之间,提交的任务会拒绝。
修饰变量,当此变量发生改动时,其他线程会知晓。变量不会被缓存到寄存器上或者其他处理器不可见的地方,因此每次读取volatile修饰的变量(都是从内存中读取),返回的都是最新值。
volatile修饰的变量不具有原子性,具有可见性和有序性。
5. synchronized 关键字的用法、优缺点。可以修饰代码块、方法、类。
不能判断是否获取锁,如果发生异常,会自动释放锁。
synchronized与volatile的区别:(1)Lock接口实现类一共有三个:
ReentrantLock和ReentrantReadWriteLock类里面的两个静态内部类:WriteLock和ReadLock
lock可以修饰代码块,使用unclock释放锁。若发生异常,并且没有使用unclock释放锁(在finally里面释放),会造成阻塞
7. 可重入锁(ReentrantLock)的用处及实现原理。(1)可重入锁原理:可以重复进入的锁。加锁时会判断,锁是否已经被获取了。如果是,判断是否是当前线程获取的。如果是,那么获取锁的次数加1,否则需要等待。
释放锁时,会给获取锁的次数减1,如果次数是0时,需要调用锁的唤醒方法,让其他被锁上的阻塞线程得到执行机会。
(2)在java环境下,ReentrantLock和Synchronized都是可重入锁。在一个类中,如果Synchronized方法1调用了Synchronized方法2,那么Synchronized方法2会正常执行。
若不然,方法2想获取锁时,该锁已经在方法1执行时获取到了,方法2一直获取不到锁资源,就一直得不到执行。
(3)用处:线程需要多次进入临界区时,会需要用到可重入锁。如:Synchronized方法1,想要调用另一个Synchronized方法时。
(4)可重入锁(可重入锁)可以指定是公平锁还是非公平锁。Synchronized只能是非公平锁。
补充:公平锁:按照等待时间长短,等待时间长优先获取锁。非公平锁:按照优先级判断,优先级越高,越先获取锁。
8. 悲观锁、乐观锁的优缺点。(1)乐观锁:乐观锁一般会使用版本号机制或CAS算法实现。
认为每次读都没有人修改,每次都不会上锁。但在修改的时候,会判断别人是否修改数据,可以使用版本号机制。
适用于:多读(少写),一般数据库提供的都是乐观锁。 在Java中java.util.concurrent.atomic包下面的原子变量类就是基于CAS实现的乐观锁。
优点:用于多读场景,冲突少,不上锁,减少锁的开销,增加数据库吞吐量。
缺点:用于多写场景,冲突多,导致上层应用不断retry,降低性能;
ABA,如果一个变量开始的值是A,后来变成B,后来又变成A,这时CAS操作不认为这个变量值被改变过;
自旋时间长(如果不成功就一直循环执行,直到执行成功),开销大;
(2)悲观锁
认为每次读都会有人修改,所以每次都上锁,同时别人就读取不到,直到获取到锁。
适用于:多写(少读的场景),传统的关系型数据库里面就用到了很多悲观锁,如行锁,表锁,读锁,写锁等。 Java中Synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
优点:用于多写的场景
缺点:有锁开销大
CAS算法:比较并交换(Compare-and-Swap,CAS)
CSA有三个操作数,内存地址V,旧的预期值A,新值B。当执行操作时,只有当V等于A时,才把V的值更新为B。
补充:关于synchronized和CAS比较,
若是资源竞争较少(线程冲突比较轻),用CAS。synchronized需要挂起、唤醒线程,比较消耗资源,但是CAS只在外层操作,不需要进入内核,不需切换线程,自旋操作比较少,有更高的性能。
若是资源竞争比较严重(线程冲突严重),用synchronize比较好。用CAS自旋发生概率大,消耗CPU,效率低于synchronized
(1)使用join方法,(join方法:如果在线程a中调用线程b.join(),线程a会一直等待,直到线程b执行结束时,线程a才会执行)
join():参数,如果是10,线程a会等待10毫秒,让线程b执行,10毫秒结束之后,线程a、b并行执行。注意:参数为0,和没有参数一样,线程a一直等待线程b结束之后,再执行。
该方法主要作用就是同步,使线程之间的并行执行改成串行执行。
调用join方法之前必须调用start方法,因为执行完start方法,线程才开始创建。如果线程都没开启,自然也不能同步。
join方法原理:相当于在a线程中,调用了b线程的wait方法,使a线程进行等待,当b线程执行完成时(或到达等待时间),再调用b线程的notifyall方法,唤醒a线程,之后a线程再继续执行
(2)使用单个线程池(newSingleThreadExecutor)方法:这个线程处理完,接着处理下一个线程,发生异常时,将会有新的线程代替。
public class ThreadTest {
public static void main(String [] args){
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1...");
}
},"T1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2...");
}
},"T2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3...");
}
},"T3");
//方法1:使用join方法
t3.start();
t2.start();
t1.start();
//方法2:使用线程池的newSingleThreadExecutor方法
// ExecutorService executorService = Executors.newSingleThreadExecutor();
// executorService.submit(t1);
// executorService.submit(t2);
// executorService.submit(t3);
// executorService.shutdown();
}
(1)未开始的
(2)正在运行的
(3)阻塞的
(4)永久等待的
(5)等待一定时间的
(6)运行结束的
sleep 方法,不释放锁,定时会自己唤醒。方法是由Thread提供的。
wait方法释放锁资源,需要使用notify(唤醒一个线程)或者notifyall 唤醒所有线程。是由Object类提供的。
(1)ThreadLocal叫做线程本地变量或者线程本地存储,为变量在每个副本中都创建一个副本,使每个线程都可以访问自己的变量副本,互相不冲突。
线程安全,但是资源消耗可能会比较大。
(2)ThreadLocal提供的几个方法:每个threadlocal 只能存储一个变量副本,如果想要一个线程有多个变量副本,则需要创建多个Threadlocal。
public T get() { } //获取ThreadLocal在当前线程中保存的变量副本
public void set(T value) { }//设置当前线程中的变量副本
public void remove() { }//移除当前线程中的变量副本
protected T initialValue() { }//protected类型,一般用于在使用是重写的,延迟加载的方法
(3)应用场景:解决数据库连接和session连接等
如图:
(1)IoC(Inversion of Control)是控制反转,由IoC容器去创建对象,并且注入依赖对象。
(2)传统方式是在类内主动创建需要依赖的对象,耦合度较高;
而有了IOC之后,创建和查找依赖对象以及对象销毁的控制权交给了spring的IOC容器,耦合性较低,增加程序的复用性和灵活性。
(3)IoC主要分为两个阶段:容器启动阶段和bean实例化阶段。
A.依赖注入方式:
接口注入:基本不用
构造方法注入:马上进入就绪状态。但是java构造方法不能被继承,不能设置默认值。依赖对象比较多时,构造参数列表比较长。
setter方法注入:不能立马进入就绪状态。可以被继承和设置默认值。
B.Spring提供了两种IoC容器:ApplicationContext是BeanFactory的子类,二者是继承关系。
BeanFactory:默认采用延时策略,只有当客户端对象需要访问哪个对象时,才初始化该对象并注入依赖。所以容器初期启动比较快,所需资源有限。
ApplicationContext:除了具有BeanFactory特性之外,还提供了其他高级特性,如事件发布,国际化信息支持等。在启动容器时,
会加载所有管理的对象并且全部初始化并绑定完成。所以相对BeanFactory来说,ApplicationContext需要更多的系统资源,启动也较之稍微慢一些。
C.通知容器关于关于依赖对象的信息方式:通常使用xml文件方式和注解方式。
xml文件方式:
注解方式:
Ioc(控制反转)和DI(依赖注入)是同一个概念的不同描述。
IOC原理:IOC最基本的技术就是反射(根据给出的类名,动态的生成对象机制的工厂模式。一个类注入另一个类对象并不需要自己生成,Ioc会代替这个类生成需要注入的对象。
AOP原理:面向切面编程。主要用于事务管理、日志、缓存、安全机制等。
6. Spring Bean 的生命周期。 7. SpringMVC 的 Controller 是如何将参数和前端的数据一一对应的。(1)快速排序:
2. 三大查找算法基本思想和 Java 语言实现。 3. 栈、队列、链表、树基本数据结构及 Java 语言实现。 4. B、B+树的原理。 5. 一致性 Hash 算法,及其应用。 6.java为什么要实现序列化。序列化是把javabean转换成字节流,可以进行网络传输,
使用场景:把实体类保存到数据库;写文件需要实现序列化;远程调用
远程调用时,会把对象序列化,然后传输给另一个项目,然后另一个项目对这个对象进行反序列化
事务、锁、索引原理和各种优化。
数据库事务隔离级别。
原子性、一致性、隔离性、持久性(ACID)
mysql 主从复制的原理
分库分表:非分表主键跨库查询、跨库分页、分页查询优化。
SQL优化的常见方法有哪些?
垃圾回收原理和优缺点。
各种 GC 原理区别、如何确定那些对象应该回收方法。
JVM 的内存结构
Java 类的加载过程。
(1)
(2)类加载过程是指:jvm把字节码文件加载并进内存,并解析生成对应的class对象的过程。
不会重复加载类
常用的 Linux 命令以及参数。
Vim