- 请你说明ConcurrentHashMap有什么优势,1.7和1.8有什么区别
参考链接: https://www.cnblogs.com/like-minded/p/6805301.html
- 请你说明一下TreeMap的底层结构?
TreeMap的底层的数据结构是红黑树,红黑树的特性如下
1.根节点是黑色的。
2.每个节点都只能是红色 or 黑色。
3.每个叶节点(NIL节点,空节点)都是黑色的。
4.如果一个节点是红色的,则它两个子节点都是黑色的,一条路径上不能出现两个连续红节点
5.从任一节点到其每个叶子节点的所有路径包含相同数据的黑色节点
详细参考链接: https://my.oschina.net/90888/blog/1626065
- 请说明ConcurrentHashMap的锁加到了哪些地方?
ConcurrentHashMap在1.7的时候采用了分段锁的思想
在java1.7的时候对每个Segment进行加锁(Segment 继承自 ReentrantLock),在并发的时候,锁在segment上进行生效,依旧可以读写其他的段数据。
ConcurrentHashMap在1.8的时候采用了Node + CAS + synchronized
由synchronized 锁住一个Node[] table 数组中的元素,通过CAS进行数据的更新 or 插入
- 请你解释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
- 请你简单介绍一下ArrayList和LinkedList的区别,并说明如果一直在list底部添加元素,用哪种方式的效率高?
ArrayList采用数组实现的,查找元素的效率比LinkedList高。
LinkedList采用双线链表实现,插入和删除的效率比ArrayList要高。
当添加数据的时候,LinkedList会在尾部new一个Node存储新添加的数据,所以当数据量小的时候,这个时间并不是很明显,而ArrayList需要扩容,因此当数据量小的时候,LinkedList的效率是高于ArrayList的(小于千万级别)
但是当数据量很大的时候,new的时间高于扩容的时间,ArrayList的效率高于LinkedList(大于千万级别)
- 如果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 {
public int hashCode() {
return name.hashCode() + age * 10;
}
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();
}
}
- 请你解释一下hashMap的具体实现
找到了一篇大佬写的 给大家参考一下: 最好把内部的源码和put() get() 和扩容机制搞懂..
参考链接: https://www.cnblogs.com/tianzhihensu/p/11972780.html
上述链接有一些小错误的地方:
1. 第一张类图,HashTable的继承类写错了 是Dictionary不是Directory
2. 1.7的哈希表组成是 数组 + 链表
- 请你说明一下Map 和 ConcurrentHashMap的区别?
在java1.7环境下
Hashmap是线程不安全的,put时在多线程情况下,会形成环从而导致死循环和数据覆盖问题。
CoucurrentHashMap是线程安全的,采用分段锁机制,减少锁的粒度。
在java1.8环境下
HashMap依旧线程不安全(会发现数据覆盖的情况),ConcurrentHashMap使用 Node + cas + synchronized保证线程安全
- 如何保证线程安全?
1. 线程安全需要保证的特性
* 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
* 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
* 有序性:程序执行的顺序按照代码的先后顺序执行
2、为什么线程不安全?
* 缓存导致的可见性问题
* 线程切换带来的原子性问题
* 编译优化带来的有序性问题
3、如何解决
* JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
* synchronized、volatile、LOCK,可以解决可见性问题
* Happens-Before 规则可以解决有序性问题
详细链接: https://blog.csdn.net/weixin_40459875/article/details/80290875
- 请你简要说明一下线程的基本状态以及状态之间的关系
- 请你解释一下什么是线程池(Thread pool)?
创建一个对象要获取内存资源或其他更多资源。在Java中更加如此,虚拟机将视图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象的创建和销毁,这就是“池化资源”技术产生的原因。
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程,而是返回池中从而减少创建和销毁对象的开销。
线程池的七大参数
package com.liz.juc.thread_pool.relearn_thread_pool;
import java.util.concurrent.*;
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;
}
}
}
}
}