juc补充

juc

HashMap 和 HashTable 的区别有哪些?

答:HashMap 没有考虑同步,是线程不安全的;HashTable 使用了 synchronized 关键字,是线程安全的;前者允许 null 作为 Key;后者不允许 null 作为 Key。

ConcurrentHashMap 和 HashTable 的区别? (必问)

ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,在插入的时候没有考虑同步,1.8以前是头插法,1.8以后就考虑尾插法(不容易出现环形链表,的,但是容易出现数据丢失的情况)。HashTable 考虑了同步的问题,但是 HashTable 在每次同步执行时都要锁住整个结构。具体锁this这里。ConcurrentHashMap 锁的方式是稍微细粒度的,同时将 Hash 表分为 16 个桶(默认值),诸如 get、put、remove 等常用操作只锁当前需要用到的桶。

ConcurrentHashMap 的具体实现知道吗?

答:该类包含两个静态内部类 HashEntry 和 Segment;前者用来封装映射表的键值对,后者用来充当锁的角色;
Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个 HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。

HashMap 的长度为什么是 2 的幂次方?

通过将 Key 的 hash 值与 length−1 进行 & 运算,实现了当前 Key 的定位,2 的幂次方可以减少冲突(碰撞)的次数,提高 HashMap 查询效率。如果 length 为 2 的次幂,则 length−1 转化为二进制必定是 11111…的形式,在于 h 的二进制与操作效率会非常的快,而且空间不浪费;如果 length 不是 2 的次幂,比如 length 为 15,则 length−1 为 14,对应的二进制为 1110,在于 h 与操作,最后一位都为 0,而 0001、0011、0101、1001、1011、0111、1101 这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。

List 和 Set 的区别是什么?

答:List 元素是有序的,可以重复;Set 元素是无序的,不可以重复。

List、Set 和 Map 的初始容量和加载因子是什么

a. ListArrayList 的初始容量是 10;加载因子为 0.5;扩容增量:原容量的 0.5 倍 + 1;一次扩容后长度为 16。Vector 初始容量为 10,加载因子是 1。扩容增量是原容量的 1 倍,如 Vector 的容量为 10,一次扩容后是容量为 20。
b. SetHashSet 初始容量为 16,加载因子为 0.75;扩容增量是原容量的 1 倍,如 HashSet 的容量为 16,一次扩容后容量为 32。
c. MapHashMap 初始容量 16,加载因子为 0.75;扩容增量是原容量的 1 倍;如 HashMap 的容量为 16,一次扩容后容量为 32。加载因子是泊松分布算出来的,为什么会是这个,这是因为这是实操出来,所以编程都是大量实践出来的

Comparable 接口和 Comparator 接口有什么区别?

答:

前者简单,但是如果需要重新定义比较类型时,需要修改源代码。
后者不需要修改源代码,自定义一个比较器,实现自定义的比较方法。

多线程和单线程的区别和联系

答:在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用 CPU 的机制。多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间。

如何指定多个线程的执行顺序?

解析:面试官会举例子,如何让 10 个线程按照顺序打印 0123456789?(写代码实现)答:设定一个 orderNum,每个线程执行结束之后,更新 orderNum,指明下一个要执行的线程,并且唤醒所有的等待线程。在每一个线程的开始,要 while 判断 orderNum 是否等于自己的要求值!不是,则 wait,是则执行本线程。

线程和进程的区别是什么(必考)

答:进程是一个“执行中的程序”,是系统进行资源分配和调度的一个独立单位。线程是进程的一个实体,一个进程中拥有多个线程,线程之间共享地址空间和其他资源(因此通信和同步等操作线程比进程更加容易)线程上下文的切换比进程上下文切换要快很多:进程切换时,涉及到当前进程的 CPU 环境的保存和新被调度运行进程的 CPU 环境的设置;线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。

多线程产生死锁的 4 个必要条件?

答:互斥条件:一个资源每次只能被一个线程使用请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
资源有限,循环等待,请求保持,不可剥夺

接问:如何避免死锁?(经常问)答:指定获取锁的顺序,举例如下:比如某个线程只有获得 A 锁和 B 锁才能对某资源进行操作,在多线程条件下,如何避免死锁?获得锁的顺序是一定的,比如规定,只有获得 A 锁的线程才有资格获取 B 锁,按顺序获取锁就可以避免死锁!
线程活跃态:活锁和饥饿,死锁
饥饿:线程一直得不到执行的机会

sleep( ) 和 wait(n)、wait( ) 的区别是什么?

答:sleep() 方法:是 Thread 类的静态方法,当前线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进行可运行状态,等待 CPU 的到来(睡眠不释放锁,如果有的话)。wait() 方法:是 Object 的方法,必须与 synchronized 关键字一起使用,线程进入阻塞状态,当 notify 或者 notifyall 被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠时,释放互斥锁。

synchronized 关键字是什么?

答:底层实现:进入时,执行 monitorenter,将计数器 +1,释放锁 monitorexit 时,计数器 −1;当一个线程判断到计数器为 0 时,则当前锁空闲,可以占用;反之,当前线程进入等待状态。含义:(monitor 机制)Synchronized 是在加锁,加对象锁。对象锁是一种重量锁(Monitor),Synchronized 的锁机制会根据线程竞争情况在运行时会有偏向锁(单一线程)、轻量锁(多个线程访问 Synchronized 区域)、对象锁(重量锁,多个线程存在竞争的情况)、自旋锁等,该关键字是一个几种锁的封装。

volatile 关键字

答:该关键字可以保证可见性不保证原子性。功能:主内存和工作内存,直接与主内存产生交互,进行读写操作,保证可见性禁止 JVM 进行的指令重排序解析:关于指令重排序的问题,可以查阅 DCL 双检锁失效相关资料。

ThreadLocal(线程局部变量)关键字

答:当使用 ThreadLocal 维护变量时,其为每个使用该变量的线程提供独立的变量副本,因此每一个线程都可以独立改变自己的副本,而不会影响其他线程对应的副本。ThreadLocal 内部实现机制:每个线程内部都会维护一个类似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干的 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程。Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用是:为其属主线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系。Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。

对线程池有了解吗?(必考)

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出在务来执行。
答:java.util.concurrent.ThreadPoolExecutor 类就是一个线程池。客户端调用 ThreadPoolExecutor.submit(Runnable task) 提交任务,线程池内部维护的工作者线程的数量就是该线程池的线程池大小,有 3 种形态:当前线程池大小,表示线程池中实际工作者线程的数量;最大线程池大小(maxinumPoolSize),表示线程池中允许存在的工作者线程的数量上限;核心线程大小(corePoolSize),表示一个不大于最大线程池大小的工作者线程数量上限。如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队;如果运行的线程等于或者多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不是添加新线程;如果无法将请求加入队列,即队列已经满了,则创建新的线程,除非创建此线程超出 maxinumPoolSize,在这种情况下,任务将被拒绝。

线程池底层原理

1.在创建了线程池后,等待提交过来的任务请求。
2.当调用execute()方法添加一个请求任务时,线程池会做如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务成入队列;
2.3如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

juc包(必考考了几次了)

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值