java基础面试题系列(81-90)

  1. 请你说明ConcurrentHashMap有什么优势,1.7和1.8有什么区别
参考链接: https://www.cnblogs.com/like-minded/p/6805301.html
  1. 请你说明一下TreeMap的底层结构
TreeMap的底层的数据结构是红黑树,红黑树的特性如下
1.根节点是黑色的。
2.每个节点都只能是红色 or 黑色。
3.每个叶节点(NIL节点,空节点)都是黑色的。
4.如果一个节点是红色的,则它两个子节点都是黑色的,一条路径上不能出现两个连续红节点
5.从任一节点到其每个叶子节点的所有路径包含相同数据的黑色节点

详细参考链接: https://my.oschina.net/90888/blog/1626065

  1. 请说明ConcurrentHashMap的锁加到了哪些地方?
ConcurrentHashMap在1.7的时候采用了分段锁的思想
在java1.7的时候对每个Segment进行加锁(Segment 继承自 ReentrantLock),在并发的时候,锁在segment上进行生效,依旧可以读写其他的段数据。

ConcurrentHashMap在1.8的时候采用了Node + CAS + synchronized
由synchronized 锁住一个Node[] table 数组中的元素,通过CAS进行数据的更新 or 插入
  1. 请你解释HashMap的容量为什么是2的n次幂?
首先看一下如何向hashmap中放入数据(部分源码)

if((p = tab[i = (n - 1) & hash]) == null) 
	tab[i] = new Node(hash,key,value,null)

HashMap的容量时2的n次幂 和 (n - 1) & hash关系很大,如果HashMap的容量是2的n次幂的话,首先使用&运算比使用取余效率高

其次n - 1二进制为1111...1,和添加元素的哈希值进行计算的时候,可以充分散列(添加的元素会充分分布在HashMap的每个位置上,减少Hash碰撞),避免生成了类似链表的结构

参考链接: https://blog.csdn.net/apeopl/article/details/88935422
  1. 请你简单介绍一下ArrayList和LinkedList的区别,并说明如果一直在list底部添加元素,用哪种方式的效率高?
ArrayList采用数组实现的,查找元素的效率比LinkedList高。

LinkedList采用双线链表实现,插入和删除的效率比ArrayList要高。

当添加数据的时候,LinkedList会在尾部new一个Node存储新添加的数据,所以当数据量小的时候,这个时间并不是很明显,而ArrayList需要扩容,因此当数据量小的时候,LinkedList的效率是高于ArrayList的(小于千万级别)

但是当数据量很大的时候,new的时间高于扩容的时间,ArrayList的效率高于LinkedList(大于千万级别)
  1. 如果HashMap的key是一个自定义的类,怎么办?
1.如果key是自定义的类,就必须要重写hashCode()equals()

2.equals()hashCode()的作用

equals()
public boolean equals(Object obj){
	return (this == obj);//判断两个对象是否是相等的
}

hashCode()
public native int hashCode();//是一个本地方法,返回对象的地址值

3.什么时候使用hashCode()equals()
HashMap对于每一个对象而言,通过其hashCode()方法可以生成一个散列码,该散列码经过处理后,会放入Entry中合适的位置(存放key value)

equals()方法则是在HashMap中插入值或者查询的时候会用到,当HashMap中插入值 或者 查询值对应的散列码的时候,通过equals比较key值是否相等,所以如果想以自建对象作为key的时候需要重写equals()hashCode()
    
4. 如果不重写会发生什么情况?
* 两个对象明明是相等的,但是hashcode不相等(不重写 hashCode)

5. 总结
一般对于存放到Set集合或者Map中键值对的元素,需要按需要重写hashCode与equals方法,以保证唯一性!


class Person {  
     //都一样,变化的就是下面的  
     //重写hashCode()方法
      public int hashCode() {  
          return name.hashCode() + age * 10;  
     } 
     //重写equals()方法2
     public boolean equals(Object obj) {  

         if (!(obj instanceof Person)){
         throw new ClassCastException("类型不匹配");  
         Person p = (Person) obj;  
         return this.name.equals(p.getName()) && this.age == p.getAge();  
    }  
 }  
  1. 请你解释一下hashMap的具体实现
找到了一篇大佬写的 给大家参考一下: 最好把内部的源码和put() get() 和扩容机制搞懂..
参考链接: https://www.cnblogs.com/tianzhihensu/p/11972780.html

上述链接有一些小错误的地方: 
1. 第一张类图,HashTable的继承类写错了 是Dictionary不是Directory
2. 1.7的哈希表组成是 数组 + 链表
  1. 请你说明一下Map 和 ConcurrentHashMap的区别?
在java1.7环境下
Hashmap是线程不安全的,put时在多线程情况下,会形成环从而导致死循环和数据覆盖问题。
CoucurrentHashMap是线程安全的,采用分段锁机制,减少锁的粒度。

在java1.8环境下
HashMap依旧线程不安全(会发现数据覆盖的情况),ConcurrentHashMap使用 Node + cas + synchronized保证线程安全
  1. 如何保证线程安全?
1. 线程安全需要保证的特性
* 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
* 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
* 有序性:程序执行的顺序按照代码的先后顺序执行

2、为什么线程不安全?
* 缓存导致的可见性问题
* 线程切换带来的原子性问题
* 编译优化带来的有序性问题

3、如何解决
* JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
* synchronized、volatile、LOCK,可以解决可见性问题
* Happens-Before 规则可以解决有序性问题

详细链接: https://blog.csdn.net/weixin_40459875/article/details/80290875
  1. 请你简要说明一下线程的基本状态以及状态之间的关系

java基础面试题系列(81-90)_第1张图片

  1. 请你解释一下什么是线程池(Thread pool)?
创建一个对象要获取内存资源或其他更多资源。在Java中更加如此,虚拟机将视图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象的创建和销毁,这就是“池化资源”技术产生的原因。
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程,而是返回池中从而减少创建和销毁对象的开销。

线程池的七大参数

package com.liz.juc.thread_pool.relearn_thread_pool;

import java.util.concurrent.*;

/**
 * 自定义线程池的七大参数:
 * 1.corePoolSize : 核心线程数,先来的线程可以直接为核心线程,当线程数 在corePoolSize和maximumPoolSize之间的时候,其他线程放入队列中
 * 如果超过了maxiumPoolSize,采用拒绝策略
 * 

* 2.maximumPoolSize : 最大线程数,在系统线程池中的CacheThreadPool方案,该值为Integer.MAXVALUE 很容易出现oom *

* 3.KeepAliveTime : 线程池中空闲线程等待工作的超时时间,单位是ns,一旦超时,直接进行销毁 *

* 4.TimeUnit.SECONDS : 设置超时时间 *

* 5.BlockingQueue workQueue : 默认的消息队列(存放闲置线程) *

* 6.ThreadFactory threadFactory : 线程工厂,设置为默认工厂即可 Executors.defaultThreadFactory() *

* 7.RejectedExecutionHandler handler : 拒绝策略 * new ThreadPoolExecutor.AbortPolicy() 直接抛出异常 * new ThreadPoolExecutor.CallerRunsPolicy() 调节机制,多出的线程回退给调用者线程 哪个线程调用了就给谁去处理 * new ThreadPoolExecutor.DiscardOldestPolicy() 丢弃最老的一个请求(最先入队列的那几个),并尝试再次提交任务 * new ThreadPoolExecutor.DiscardPolicy() 默默丢失不能处理的任务【不予任何处理】 */ public class MyThreadPool { public static void main(String[] args) throws InterruptedException { testMyThreadPool(); } public static void testMyThreadPool() throws InterruptedException { ExecutorService myPool = new ThreadPoolExecutor( 2, 8, 2L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); try { for (int i = 0; i < 10; i++) { myPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t 在工作"); }); } } catch (Exception e) { e.printStackTrace(); } finally { myPool.shutdown(); } ThreadSafe safe = new ThreadSafe(); safe.start(); System.out.println("running"); System.out.println(); Thread.sleep(2000); safe.interrupt(); } static class ThreadSafe extends Thread{ @Override public void run() { while(!isInterrupted()){ try { Thread.sleep(1000); System.out.println("I am alive"); } catch (InterruptedException e) { e.printStackTrace(); break;//捕捉到中断异常 直接白给 } } } } }

你可能感兴趣的:(java基础面试题系列)