1.string 、stringbuffer、 stringbuilder区别
string是长度是不可变的,stringbuffer、 stringbuilder是长度可以变的,
stringbuffer是线程安全的,stringbuilder是线程不安全
2.常用的集合类、特点、底层实现
set、map、list,hashset、treeset、arraylist、linedlist、hashmap、treemap、linkedhashmap
set是无序不可重复;
hashset是无序不可重复,底层是hashmap;
treeset是有序不可重复,底层是二叉树
list是有序可重复的,
arraylist是有序可重复,底层是数组,查询快;
linkedlist是有序重复,底层是链表,增删快
map底层是键值对,是数组加链表,
hashmap是无序的不线程安全,可以都null,
hashtable是线程安全的,不可以null;
treemap底层是二叉树,有序,
linkedhashmap底层是链表,有序;
concurrenthashmap是hashmap的线程安全的实现
3.有没有看过hashmap的源码、长度、填充因子
HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75,
HashMap扩容时是当前容量翻倍即:capacity*2,HashMap的最大容量是2的n次
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,
根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,
那么在这个位置上的元素将以链表的形式存放,
新加入的放在链头,最先加入的放在链尾。(jdk7是头插法)
jdk8是尾插法,长度超过8个,会变成红黑树。
如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上
4.hashmap的线程安全实现
hashmap不是线程安全的,可以设值为null,hashtable是线程安全的,不可以设值为null,
hashmap的线程安全实现是concurrentHashmap
5.concurrentHashmap的原理
把hahmap分成16份,每段进行分段锁,1.8jdk改成红黑树,底层是数组+链表+红黑树(长度大于8)
6.对数组按照大小排序方法
1.数组的冒泡排序 2.数组的插入排序 3.Arrays.sort()
Arrays.sort()用的是什么排序算法?原理是什么?时间复杂度?
Arrays.sort()用的是快速排序算法。
算法的思想: 分治法:选择基准(一般选第数组一个)将数组一分为二,基准前面的比基准小,基准后面的比基准大,
之后分别对这两部分继续之前的操作,已达到整个数组有序的目的。
时间复杂度:O(nlogn)
冒泡排序:
空间复杂度最好情况是:O(n)
空间复杂度最差情况是:O(n^2)
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {6,3,8,1,9,5};
for (int num : arr){
System.out.print(num);
}
for (int i=0;i<arr.length-1;i++){
for (int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] =temp;
}
}
}
System.out.println();
for (int num : arr){
System.out.print(num);
}
}
}
7.collections中的sort方法的内部实现
基于compareTo()方法
8.compareTo返回的是什么类型
compareTo就是比较两个值,如果前者大于后者,返回1,等于返回0,小于返回-1
9.websocket的实现原理,是长连接还是短链接
websocket基于http,基于一次http握手,长连接
10.http 、tcp、socket的关系
IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,
tcp/ip是基于socket的,socket则是对TCP/IP协议的封装和应用
11.tcp三次握手和四次挥手
第一次握手:客户端发送syn包到服务器,等待服务器确认;
第二次握手:服务器收到syn包,同时自己也发送一个SYN+ack包;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,完成三次握手。
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,设置服务器定时,并将确认序号设置为收到序号加1。
13.java8的新特性
1.lambda表达式和函数式接口
2.接口的默认方法和静态方法
3.方法引用(class::new)
4.新的date和time时间api
5.stream的api
6.重复注解
14.写一个简单的lambda表达式语句
// Java 8之前:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(“Before Java8, too much code for too little to do”);
}
}).start();
//Java 8方式:
new Thread( () -> System.out.println(“In Java8, Lambda expression rocks !!”) ).start();
15.sleep和wait区别
sleep()是线程的方法,调用会暂停此线程指定的时间,不会释放对象锁,到时间自动恢复;
wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()
唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态
16.notify和notifyAll区别
notify只会通知一个在等待的对象,而notifyAll会通知所有在等待的对象,并且所有对象都会继续运行
17.消息队列传送模式?
1.点对点模式
2.发布/订阅模式
18.cookie和session区别?
cookie保存在客户端,session保存在服务器端,
cookie目的可以跟踪会话,也可以保存用户喜好或者保存用户名密码
session用来跟踪会话、用户验证
19.消息中间件用过哪些?
rabbitMQ:数据一致性、稳定性和可靠性要求很高
kafka更适合IO高吞吐的处理,比如ELK日志收集
20.乐观锁和悲观锁
乐观锁
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。一般使用version方式和CAS操作方式。
CAS操作方式:
即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
CAS缺点:ABA问题(通过家version解决)
乐观锁使用场景
比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。 读锁
悲观锁
每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。在Java中,synchronized的思想也是悲观锁。
悲观锁使用场景
比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。 写锁
21.CAS和AQS
CAS即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
可能会出现ABA问题:
如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。比如链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
所以JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。
AQS: 队列同步器是用来构建锁或者其他同步组件的基础框架。AQS的主要作用是为Java中的并发同步组件提供统一的底层支持,例如ReentrantLock,CountdowLatch就是基于AQS实现的,用法是通过继承AQS实现其模版方法,然后将子类作为同步组件的内部类。
AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架
AQS同时提供了独占模式和共享模式两种不同的同步逻辑。
state的访问方式有三种:
getState()
setState()
compareAndSetState()
AQS:
acquire(int arg)
tryAcquire(int arg) (独占)
tryRelease(int arg)(独占)
tryAcquireShared(共享)
tryReleaseShared(int arg) (共享)
重入锁(ReentrantLock)都可重入性基于AQS(独占)实现:公平锁、非公平锁,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
Semaphore/CountDownLatch 基于AQS(共享)实现,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。