面试宝典问题

是否了解LinkedHashMap

LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。

在遍历的时候会比HashMap慢TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的记录是排过序的。

是否了解CopyOnWriteArrayList

一、CopyOnWriteArrayList介绍
①、CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
②、CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差。
③、CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用 ,因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

二、CopyOnWriteArrayList 有几个缺点:
1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc。
(1、young gc :年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或叫Young GC。
2、年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行Major GC,也叫 Full GC

2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;可能有朋友说ArrayList是非线程安全的容器,换成Vector就没问题了,实际上换成Vector还是会出现这种错误。

原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也即是说expectedModCount是每个线程私有。假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。

是否了解ConcurrentModificationException异常

Java ConcurrentModificationException异常原因和解决方法

在前面一篇文章中提到,对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常。下面我们就来讨论以下这个异常出现的原因以及解决办法。

以下是本文目录大纲:

一.ConcurrentModificationException异常出现的原因

二.在单线程环境下的解决办法

三.在多线程环境下的解决方法

ArrayList<Integer> list = new ArrayList<>();
list.add(2);
 Iterator<Integer> iterator = list.iterator();
 while(iterator.hasNext()){
     Integer integer = iterator.next();
     if(integer == 2){
         list.remove(integer);
     }
 }

先在next()方法中会调用checkForComodification()方法,然后根据cursor的值获取到元素,接着将cursor的值赋给lastRet,并对cursor的值进行加1操作。初始时,cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0。注意此时,modCount为0,expectedModCount也为0。

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();

通过remove方法删除元素最终是调用的fastRemove()方法,在fastRemove()方法中,首先对modCount进行加1操作(因为对集合修改了一次),然后接下来就是删除元素的操作,最后将size进行减1操作,并将引用置为null以方便垃圾收集器进行回收工作。

那么注意此时各个变量的值:对于iterator,其expectedModCount为0,cursor的值为1,lastRet的值为0。

对于list,其modCount为1,size为0。

接着看程序代码,执行完删除操作后,继续while循环,调用hasNext方法()判断,由于此时cursor为1,而size为0,那么返回true,所以继续执行while循环,然后继续调用iterator的next()方法:

注意,此时要注意next()方法中的第一句:checkForComodification()。

关键点就在于:调用list.remove()方法导致modCount和expectedModCount的值不一致。

在单线程环境下的解决办法

在这个方法中,删除元素实际上调用的就是list.remove()方法,但是它多了一个操作:

expectedModCount = modCount;

因此,在迭代器中如果要删除元素的话,需要调用Itr类的remove方法。

在多线程环境下的解决方法

一般有2种解决办法:

1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;

2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

ConcurrentHashMap

ConcurrentHashMap

  1. 在理想状态下,ConcurrentHashMap 可支持16个线程执行并发写操作,及任意数量线程的读操作。

  2. 关于它的存储结构

    1. JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。
    2. JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry<K,V>)。锁粒度降低了。

抽象类和接口

  1. 抽象类可以提供成员方法的实现细节,而接口中没有;但是JDK1.8之后,在接口里面可以定义default方法,default方法里面是可以具备方法体的。
  2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型;
  3. 接口中每一个方法也是隐式指定为 public abstract,不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法
  4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

动态代理

  1. java的动态代理技术的实现主要有两种方式:
    1. JDK原生动态代理
    2. CGLIB动态代理
  2. JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理(需要代理的对象必须实现于某个接口
  3. CGLIB通过继承的方式进行代理(让需要代理的类成为Enhancer的父类),无论目标对象有没有实现接口都可以代理,但是无法处理final的情况

类加载器有哪些

1.根类加载器

2.扩展类加载器

3.系统类加载器

常见树知道哪些

关于synchronizedvolatile的比较:

  • 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且只能修改变量,而synchronized可以修饰方法,以及代码块。
  • 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
  • volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步
  • 关键字volatile解决的下变量在多线程之间的可见性;而synchronized解决的是多线程之间资源同步问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wx6DarbF-1619110176381)(面试宝典问题.assets/20200503100214689.jpg)]

Threadlocal关键字

callable

  • callable需要配合线程池使用
  • callable比runnable功能复杂一些
    • Callable的call方法有返回值并且可以抛异常,而Runnable的run方法就没有返回值也没有抛异常,也就是可以知道执行线程的时候除了什么错误。
    • Callable运行后可以拿到一个Future对象,这个对象表示异步计算结果,可以从通过Future的get方法获取到call方法返回的结果。但要注意调用Future的get方法时,当前线程会阻塞,直到call方法返回结果。

多线程 wait(),notify()方法,案例总结

submit()和 execute()

handlerMapping和HandlerAdapter的区别

Git Flow 的正确使用姿势

mysql自定义函数和存储过程的区别

redis的如何做事务支持

问题回答

  1. Redis的事务实质上是命令的集合,在一个事务中要么所有命令都被执行,要么所有事物都不执行。
  2. 事务从开始到执行会经历以下三个阶段,MULTI 开始到 EXEC结束前,中间所有的命令都被加入到一个命令队列中;当执行 EXEC命令后,将QUEUE中所有的命令执行。也就是。
    1. MULTI开始事务。
    2. 命令入队列(QUEUE)。
    3. EXEC触发执行事务。
  3. Redis的事务没有关系数据库事务提供的回滚(rollback),所以开发者必须在事务执行失败后进行后续的处理
    1. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行
    2. 如果在一个事务中出现运行错误,那么正确的命令会被执行
  4. 此外我们可以使用DISCARD取消事务。

JVM

Eden(伊甸)空间,From Survivor (幸存) 空间,To Survivor空间(8:1:1)。

  1. 老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3
  2. 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。

PreparedStatement和Statement的区别?

谈谈对sql注入的理解。

查看错误日志

 tail -fn 100 catalina.out 循环实时查看最后100行记录(最常用的)
 
 配合着grep用, 例如 : tail -fn 100 catalina.out | grep -- '关键字'
 #vim
 1、进入vim编辑模式:vim filename
 2、输入“/关键字”,按enter键查找
 3、查找下一个,按“n”即可
 #less
 less log.log
 shift + G 命令到文件尾部 然后输入 ?加上你要搜索的关键字例如 ?1213
 shift+n 关键字之间进行切换
 

软件并发

我们首先应该知道的常见软件的并发能力

  • tomcat 调优后500左右
  • mysql并发 2000左右
  • nginx 1w左右
  • redis 5w左右

排序算法比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9JM1eyi-1619110176385)(面试宝典问题.assets/20180807094112221.png)]

冒泡排序经过优化以后,最好时间复杂度可以达到O(n)。设置一个标志位,如果有一趟比较中没有发生任何交换,可提前结束,因此在正序情况下,时间复杂度为O(n)。

选择排序在最坏和最好情况下,都必须在剩余的序列中选择最小(大)的数,与已排好序的序列后一个位置元素做交换,依次最好和最坏时间复杂度均为O(n^2)。

插入排序是在把已排好序的序列的后一个元素插入到前面已排好序(需要选择合适的位置)的序列中,在正序情况下时间复杂度为O(n)。

堆是完全二叉树,因此树的深度一定是log(n)+1,最好和最坏时间复杂度均为O(n*log(n))。

归并排序是将大数组分为两个小数组,依次递归,相当于二叉树,深度为log(n)+1,因此最好和最坏时间复杂度都是O(n*log(n))。

快速排序在正序或逆序情况下,每次划分只得到比上一次划分少一个记录的子序列,用递归树画出来,是一棵斜树,此时需要n-1次递归,且第i次划分要经过n-i次关键字比较才能找到第i个记录,因此时间复杂度是\sum_{i=1}{n-1}(n-i)=n(n-1)/2,即O(n2)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值