202510|技术日志
1009
HashMap null key
HashMap 允许 null key 的存在
因为早期HashTable不支持,HashMap作为HashTable的后续替代方案,在设计结构上引入了对null key的支持
HashMap在计算null key的散列函数时默认会将null key永远设置在0号桶,也正因此HashMap只允许一个null key
虽然map.put(null, null)
是一个合法操作,但是不推荐。因为会导致get(null)
仍然返回null,而一般情况我们使用 map ,如果返回null就下意识的认为key不存在,在上面描述的这种情况下,key存在,只是key和value都为null;
因此判断key是否存在的最佳实践是:应当使用map.containsKey()
单核CPU支持多线程吗
单核CPU是支持的,在程序执行时底层通过操作系统时间片轮转的方式,把CPU的时间片分给不同的线程执行。
由于现代CPU的处理速度一般都比较快,虽然受限于单个核心,同一个时间只能执行一个线程,但是可以快速在多个线程之间进行切换,给我们一种多个任务在同时执行的”多线程”的感觉
1010
操作系统线程调度方案
线程调度是 操作系统 管理 程序线程执行顺序 以及 系统整体资源 的一个重要机制。
主要可以分为两种调度方案:
- 非抢占式调度:任务必须主动让出CPU执行权,否则会一直占用CPU。也就是线程执行完毕后,主动通知系统切换到另一个线程
- 抢占式调度:操作系统调度器来统一控制CPU的执行权,调度器可以强制中断CPU对于当前任务的处理,将CPU分配给更高优先级的任务
对比
抢占式 | 非抢占式(协同式调度) | |
---|---|---|
切换机制 | 调度器强制中断 | 任务主动让出 |
资源开销 | 存在线程上下文切换开销 | 不存在线程上下文切换 |
死锁饥饿风险 | 低,调度器可介入干预 | 高,可能一直占用CPU资源 |
适用场景 | 对响应时间敏感的实时性场景,如现代操作系统;多线程编程场景 | 资源有限、协程或事件驱动框架 |
常见的抢占式调度机制
优先级调度:高优先级线程优先执行,Windows的线程调度使用的就是这个机制
时间片轮转:OS决定每个线程分配到固定时间片轮流执行
短任务优先:短任务可以抢占长任务的CPU执行权
单核多线程最佳实践场景
单核多线程适合IO密集型场景,而多核更适用于CPU密集型计算场景。
因为IO密集型的任务,CPU利用率低,更多时间是在等待IO设备,瓶颈在IO。采用单核多线程可以让单核的这个CPU在多个IO密集的任务之间轮转执行,最大化利用CPU;
而CPU密集型的任务,使用多核CPU运行能最大化利用CPU同时获得最大的执行效率。
1013
并发和并行的区别
并发Concurrency,对应单核CPU,多个任务同一时间只有一个任务在跑,源于操作系统的线程调度策略快速切换制造 “同时” 的错觉
并行Parallelism,对应多核CPU,多个任务实际真的在多个物理处理单元上执行
为什么ConcurrentHashMap不支持null key
关联[HashMap null key注意事项](#HashMap null key)
ConcurrentHashMap不仅不支持null key,甚至还不支持null value
二义性问题
当我们map.get(null)发现等于null的时候,就会出现一个问题:
- 是Map中不包含这个null key
- 还是Map中包含这个key,只是value是null
在HashMap中,设计之初就是为了单线程考虑的,我们可以进行这样的判断
1 |
|
但是在ConcurrentHashMap中,由于是多线程环境,并且map.get()以及map.containsKey()这两个操作是两个独立的原子操作,因此中间可能存在 “线程切换导致的状态变化”,导致判断结果不一致。
1 |
|