java实习--每天打卡十道面试题!

1、满二叉树、完全二叉树、平衡二叉树、红黑树、二叉搜索树的区别?

参考文章:树、二叉树(完全二叉树、满二叉树)概念图解

① 满二叉树

高度为 h,由 2^h-1个节点构成的二叉树称为满二叉树。

java实习--每天打卡十道面试题!_第1张图片

② 完全二叉树

完全二叉树是由满二叉树而引出来的,若二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数(即1~h-1层为一个满二叉树),第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

堆一般都是用完全二叉树来实现的。

java实习--每天打卡十道面试题!_第2张图片

③ 二叉查找树

二叉查找树是二叉树中最常用的一种类型,是为了实现快速查找的,不仅仅支持快速查找一个数,还支持快速插入和删除数据。二叉查找树的这些性能都依赖于二叉查找树的特殊结构,二叉查找树的要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都要大于这个节点的值。

④ 红黑树

红黑树的优势在于它是一棵平衡二叉查找树,对于普通的二叉查找树(非平衡二叉查找树)在极端情况下可能会退化为链表的结构,例如,当我们依次插入 3、4、5、6、7、8 这些数据时,二叉树会退化为如下链表结构:

java实习--每天打卡十道面试题!_第3张图片

红黑树是一种弱平衡二叉树(只有黑色节点完美平衡,红色节点不一定平衡),是特殊的二叉查找树(平衡二叉查找树)。

⑤ 平衡二叉树

AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过 1,和红黑树相比,AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差的绝对值不超过1)。

不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道 AVL 树适合用于插入与删除次数比较少,但查找多的情况。

2、IP地址分类

ABCDE五类,A、B、C为基本类,D、E作为多播和保留使用,为特殊地址。

  • A类地址:以0开头
  • B类地址:以10开头
  • C类地址:以110开头
  • D类地址:以1110开头
  • E类地址:以1111开头,保留地址

3、三次握手过程中可以携带数据吗?

第三次握手时可以携带数据,但是第一、二次不行。

原因:设想这样的场景,若第一次握手可以携带数据,有人要恶意攻击服务器,则他每次都可以在第一次握手中的SYN报文中放入大量数据,会让服务器花费很多时间、空间来处理报文。

也就是说:第一次握手无法放数据,保证了服务器的安全性。而第三次握手时,已经代表成功的建立了连接,从客户端携带数据到服务器也是被理解的。

4、SYN攻击是什么?

概念:Client在短时间伪造大量不存在的ip地址,向Server不断发送SYN包,Server则回复确认包,并等待Client确认,这些包将长时间占用未连接队列,导致其他正常的SYN请求因为队列满被丢弃,从而引起网络拥塞甚至系统瘫痪(Dos/DDoS攻击)

如何检测:当看到大量半连接状态,且源地址 IP 为随机时,即可断定为一次 SYN 攻击(Linux中的netstat命令)。

解决办法:缩短SYN包的过期时间,过滤网关防护、防火墙等。

5、线程调度策略?

分时调度模型、抢占式调度模型。

抢占式调度 指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,每条线程可能分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞

**协同式调度 **指的是某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制线程切换可以预知不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直 堵塞,那么可能导致整个系统崩溃。

JVM采用抢占式调度模型。

6、为什么wait、notify定义在Object中?

① JAVA提供的锁是对象级的 (每个对象都有一把称之为monitor监控器的锁),而不是线程级的,每个对象都有锁,通过线程可以获取锁,一个线程可以获取多个锁。Object 是所有对象的顶级父类,因此统一把对象锁设置为 Object 对象,这样 Jvm 就会很容易知道应该从哪个对象锁的等待池中唤醒线程。否则它根本不知道要操作的是哪一个。

 wait/notify/notifyAll 都是对象锁级别的操作,如果把 wait/notify/notifyAll 方法定义在 Thread 类中,会带来很大的局限性:

  •  比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时 wait() 方法定义到 Thread 类中,这会遇到下面两个问题:
  • 如何实现让一个线程持有多把锁呢?
  • 又如何明确线程等待的是那把锁呢?

简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。

7、为什么wait,notify必须在同步方法或同步块中被调用?

因为,wait() 方法调用时,会释放对象锁,那么一个线程调用 wait()的前提条件是,它必须拥有该对象锁,随后释放并等待,若达到了 notify()后,再进入锁。

8、yield() 和 sleep() 区别?

(1) sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会yield()方法只会给相同优先级或更高优先级的线程以运行的机会;

(2) 线程执行 sleep() 方法后转入阻塞(blocked)状态,而执行 yield() 方法后转入就绪(ready)状态

(3)sleep() 方法使用时,需要声明抛出 InterruptedException,而 yield() 方法不需要声明任何异常;

9、如果提交时,线程池队列满,会发生什么?

(1)如果使用的是无界队列 LinkedBlockingQueue,那么,可以继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务。

(2)如果使用的是有界队列 比如 ArrayBlockingQueue,任务首先会被添加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是中止策略 AbortPolicy

10、JVM调优参数参考

注:这2点仅是简单的作为参考,之后会专门去研究一下这块知识区,写文章分享给大家

① 对堆的参数调整

  • 通过 -Xms -Xmx 限定堆的最小值、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值
  • 年轻代和年老代将根据默认的比例(1:2)分配堆内存,考虑到年轻代需要频繁的进行垃圾回收(堆的空余空间比率不断变化,会导致堆内存大小取值的不断调整,触发阈值为 40%、70%)我们通常会把 -XX:newSize -XX:MaxNewSize 设置为同样大小。

年轻代和年老代设置多大才算合理?

1)更大的年轻代必然导致更小的年老代,大的年轻代会延长普通 GC 的周期,但会增加每次 GC 的时间;小的年老代会导致更频繁的Full GC。

2)更小的年轻代必然导致更大年老代,小的年轻代会导致普通 GC 很频繁,但每次的 GC 时间会更短;大的年老代会减少Full GC的频率。

如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性。

在抉择时应该根 据以下两点

  • 本着 Full GC 尽量少的原则,让年老代尽量缓存常用对象,JVM 的默认比例 1:2 也是这个道理 。
  • 通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响 Full GC 的前提下,根据实际情况加大年轻代,比如可以把比例控制在 1:1。但应该给年老代至少预留 1/3 的增长空间。

 ② 对栈的参数调整

每个线程默认会开启 1M 的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太大了,一般 256K 就足用。

理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

总结

本篇文章就到这里了,希望可以给你带来一些帮助,也希望您能够多多关注脚本之家的更多内容!

你可能感兴趣的:(java实习--每天打卡十道面试题!)