每日面试:节后来场面试练练手

1、并发和并行的区别是什么?

并发:一个处理器可以同时处理多个任务。这是逻辑上的同时发生。
并行:多个处理器同时处理多个不同的任务。这是物理上的同时发生。
有一个清晰地比喻:
并发:一个人同时吃三个苹果。并行:三个人同时吃三个苹果。

 

并发(concurrency):指同一时刻只能够执行一条指令,但是多条指令被快速的进行切换,给人造成了它们同时执行的感觉。但在微观来说,并不同同时进行的,只是划分时间段,分别进行执行。

图片

并行(parallel):在同一时刻,有多条指令在多个处理器上同时执行。

图片

2、volatile 修饰符的有过什么实践?

1,线程1执行doWork()的过程中,可能有另外的线程2调用了shutdown,所以boolean变量必须是volatile。
2,独立观察,定期 “发布” 观察结果供程序内部使用。【例如】假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。
3,线程安全的计数器,使用 synchronized 确保增量操作是原子的,并使用 volatile 保证当前结果的可见性。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。
4,单例双检锁。


总结一下,可以分为5个:作为状态位发布;一次性安全发布;独立观察;volatile bean模式;开销较低的读写锁策略。

 

3、volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?

volatile 变量提供顺序和可见性保证,例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。


某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。
volatile修饰的变量如果是对象或数组之类的,其含义是对象获数组的地址具有可见性,但是数组或对象内部的成员改变不具备可见性。

 

4、super什么时候使用?

子类的构造函数中不是必须使用super,在构造函数中,如果第一行没有写super(),编译器会自动插入.但是如果父类没有不带参数的构造函数,或这个函数被私有化了(用private修饰).此时你必须加入对父类的实例化构造.而this就没有这个要求,因为它本身就进行实例化的构造.
如果父类的构造函数是无参的,那子类构造函数会在第一行默认调用super()

下面这种情况是必须调用super()的:

1 public class Father {2      public String name;3      public Father(String name) {4             this.name = name;5     }6 }78 class Son extends Father{9       public Son(String name) {10             super(name);11 }必须调用,否则他会默认调用父类的无参构造函数,而父类的无参构造函数已经被有参的覆盖,所以找不到。

5、Array 和 ArrayList有什么区别?什么时候应该使用Array而不是ArrayList?

Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。   

 

6、数组和链表数据结构描述,各自的时间复杂度?

1、存取方式上,数组可以顺序存取或者随机存取,而链表只能顺序存取; 
2、存储位置上,数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定; 
3、存储空间上,链表由于带有指针域,存储密度不如数组大; 
4、按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n); 
5、按值查找时,若数组无序,数组和链表时间复杂度均为O(n),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn); 
6、插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可; 
7、空间分配方面:
  数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;
  链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效。

 

7、排序算法了解哪些?时间复杂度分别是?快速排序是稳定点吗?快排排对象的时候有什么问题?

1)冒泡排序:时间复杂度O(N^2)。首先从第一个元素开始到数组最后一个元素为止,对相邻的两个元素比较,如果左端元素大于右端元素,则交换这两个元素位置,此时数组最右端元素即为该数组中所有元素的最大值。接着对该数组剩下的n-1个元素进行冒泡排序,直到整个数组有序排列。

 

2)选择排序:时间复杂度O(N^2)。先从n个数字中找到最小值min1,如果最小值min1的位置不在数组的最左端(也就是min1不等于arr[0]),则将最小值min1和arr[0]交换,接着在剩下的n-1个数字中找到最小值min2,如果最小值min2不等于arr[1],则交换这两个数字,依次类推,直到数组arr有序排列。

3)快速排序:算法的时间复杂度是O(nlogn),最坏的时间复杂度O(n^2),空间复杂度O(nlogn)。通过一趟排序将待排的记录分割成独立的两部分,其中一部分记录的关键字均比另一个部分的关键字小,然后再分别对这两个部分记录继续进行排序,以达到整个序列有效。

具体做法为:设置两个指针low和high分别指向待排序列的开始和结尾,记录下基准值baseval(待排序列的第一个记录),然后先从high所指的位置向前搜索直到找到一个小于baseval的记录并互相交换,接着从low所指向的位置向后搜索直到找到一个大于baseval的记录并互相交换,重复这两个步骤直到low=high为止。

4)归并排序:时间复杂度O(n^2)。假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并。

5)插入排序:时间复杂度为O(n^2)。每次将一个待排的记录插入到前面的已经排好的队列中的适当位置。

6)希尔排序:时间复杂度O(n^1.25),空间复杂度O(1)。不稳定的排序方法。先将待排记录序列分割成为若干子序列分别进行插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序。

7)堆排序:时间复杂度O(nlogn)。

8)基数排序:时间复杂度O(d(n+rd))。

 

8、如果请求出现问题没有响应,如何定位问题,谈下思路?

一、应用服务器CPU利用率高
1)使用top命令查看占用CPU最高的进程号;

2)查看进程下所有线程信息,可以使用top,也可以使用ps命令。
top -Hp <pid>
也可以用其他命令行将 cpu 占用率高的线程找出来:ps H -eo user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu

3)找到了占用cpu最高的两个线程,要查看线程的信息, 把上面的进程信息dump下来,然后在文件查找线程信息,不是直接dump线程信息。线程对应的是栈,在栈中可以看到线程操作了哪些数据。jmap -dump:format=b,file=文件名 <pid>
注意,有时候需要把线程号转成16进制,因为dunmp的线程号可能是以16进程显示的,比如:nid=0xc46e,0x表示是16进制的数据,c46e是线程号,转成十进制是142156,则对应的线程号是142156。

二、响应时间慢
在数据库中搜索慢的sql,一般sql的慢是没加索引或者索引失效引起的,也有可能是查询方式过于复杂,表的关联关系不对,小表驱动大表,或者在sql语句中进行了大量的计算等。如果不是慢sql引起的,则需要查找程序的问题,可以通过压测工具定位到具体方法,也可以dump进程,查看是否有锁争用、死锁或者资源等待的情况。在本次压测中出现了大量dubbo服务等待数据库响应的线程,数据库的CPU利用率达到90%,导致应用的大部分进程是sleeping状态,通过查看dump下来的线程发现大部分处于Runable状态,而他们都在等待锁住同一资源(数据库)。增加数据库CPU之后,响应时间慢的问题得到解决。

三、内存泄露
怀疑是内存泄露,于是查看jvm内存回收情况:jstat -gcutil 10366 2000。
每2秒查看一次10366进程的内存回收情况,几乎是一秒2次,并且伊甸园区和年老代的内存已经使用完毕,即使1秒回收2次也不能释放,肯定有不能回收的大对象存在。下面就使用jmap工具,dump 将内存使用的详细情况输出到文件;
jmap -dump:live,format=b,file=a.log pid
dump下来的文件大概2个G,使用工具(IBM HeapAnalyzer 工具 )进行分析。

 

9、HTTP的常见错误码,分别表示什么?

200:正确的请求返回正确的结果,如果不想细分正确的请求结果都可以直接返回200。
201:表示资源被正确的创建。比如说,我们 POST 用户名、密码正确创建了一个用户就可以返回 201。
202:请求是正确的,但是结果正在处理中,这时候客户端可以通过轮询等机制继续请求。
203:请求的代理服务器修改了源服务器返回的 200 中的内容,我们通过代理服务器向服务器 A 请求用户信息,服务器 A 正常响应,但代理服务器命中了缓存并返回了自己的缓存内容,这时候它返回 203 告诉我们这部分信息不一定是最新的,我们可以自行判断并处理。
300:请求成功,但结果有多种选择。
301:请求成功,但是资源被永久转移。比如说,我们下载的东西不在这个地址需要去到新的地址。
303:使用 GET 来访问新的地址来获取资源。
304:请求的资源并没有被修改过。
308:使用原有的地址请求方式来通过新地址获取资源。
400:请求出现错误,比如请求头不对等。
401:没有提供认证信息。请求的时候没有带上 Token 等。
402:为以后需要所保留的状态码。
403:请求的资源不允许访问。就是说没有权限。
404:请求的内容不存在。
406:请求的资源并不符合要求。
408:客户端请求超时。
413:请求体过大。
415:类型不正确。
416:请求的区间无效。
500:服务器错误。
501:请求还没有被实现。
502:网关错误。
503:服务暂时不可用。服务器正好在更新代码重启。
505:请求的 HTTP 版本不支持。

 

10、HashMap中Hash是怎么解决冲突的?其他hash冲突解决的方式有哪些?

链地址法(拉链法):将所有冲突元素按照链表存储,冲突后时间复杂度变为O(1+n)n为冲突元素个数)。HashMap里面没有出现hash冲突时,没有形成单链表时,hashmap查找元素很快,get()方法能够直接定位到元素,但是出现单链表后,单个bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。
在JAVA8中,当链表元素达到8个后,会自动转化红黑树存储。

其他长途解决方式:
开放地址法:寻找下一个为空的数组下标,而后将冲突元素存储;
再散列法(二次哈希法):再次使用一个不同的哈希算法再计算一次 (第一次%16换另一个数进行%运算)。

 

11、HashMap的工作原理是什么?

HashMap的底层是用hash数组和单向链表实现的 ,当调用put方法是,首先计算key的hashcode,定位到合适的数组索引,然后再在该索引上的单向链表进行循环遍历用equals比较key是否存在,如果存在则用新的value覆盖原值,如果没有则插入到链表linkedlist的头部。HashMap的两个重要属性是容量capacity和加载因子loadfactor,默认值分布为16和0.75,当容器中的元素个数大于 capacity*loadfactor时,容器会进行扩容resize 为2n,在初始化Hashmap时可以对着两个值进行修改,负载因子0.75被证明为是性能比较好的取值,通常不会修改,那么只有初始容量capacity会导致频繁的扩容行为,这是非常耗费资源的操作,所以,如果事先能估算出容器所要存储的元素数量,最好在初始化时修改默认容量capacity,以防止频繁的resize操作影响性能。
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。HashMap在每个LinkedList节点中储存键值对对象。 

 

12、简述一致性 Hash 算法。

一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:
1、平衡性(Balance)
2、单调性(Monotonicity)
3、分散性(Spread)
4、负载(Load)
普通的哈希算法(也称硬哈希)采用简单取模的方式,将机器进行散列,这在cache环境不变的情况下能取得让人满意的结果,但是当cache环境动态变化时,这种静态取模的方式显然就不满足单调性的要求(当增加或减少一台机子时,几乎所有的存储内容都要被重新散列到别的缓冲区中)。
一致性哈希算法的基本实现原理是将机器节点和key值都按照一样的hash算法映射到一个0~2^32的圆环上。当有一个写入缓存的请求到来时,计算Key值k对应的哈希值Hash(k),如果该值正好对应之前某个机器节点的Hash值,则直接写入该机器节点,如果没有对应的机器节点,则顺时针查找下一个节点,进行写入,如果超过2^32还没找到对应节点,则从0开始查找(因为是环状结构)。

 

13、a.hashCode() 有什么用?与 a.equals(b) 有什么关系?

1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
2、如果两个对象相同,就是适用于equals(Java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;
3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;
4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。
再归纳一下就是hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。  

 

14、在线程中你怎么处理不可捕捉异常?

为了保证主线程不被阻塞,线程之间基本相互隔离,所以线程之间不论是异常还是通信都不共享。当然,因为你抓异常是主线程,而异常是在子线程出现,可以用thread.setUncaughtExceptionHandler()去处理线程的异常。在Thread中,Java提供了一个setUncaughtExceptionHandler的方法来设置线程的异常处理函数,你可以把异常处理函数传进去,当发生线程的未捕获异常的时候,由JVM来回调执行。类似的功能就可以组成线程池自己的异常处理机制,正常来说,你想在主线程异步执行子线程的代码并得知是否执行成功,可以直接使用Promise模式,即Java中线程池返回的Future对象。

另附资源下载:关注 “Java面试百分百
1,后台回复:面试资料,可获取一份最新的Java面试资料。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值