ArrayList
、Queue
、HsahMap
… 都是线程不安全的
Vector
、Stack
、Hashtable
都是线程安全的(内置了 synchronized),实际上这几个东西并不推荐使用
synchronized
,无论如何都要加锁,哪怕单线程也要加锁,不科学。很有可能出现阻塞,可能会使程序效率大打折扣ArrayList
套了个壳。ArrayList
各种操作本身不带锁,通过上述套壳之后,得到了新的对象,新的对象里面的关键方法都是带有锁的CopyOnWriteArrayList
这里面主要用到了“写实拷贝”
这种操作有很大的局限性
一般在服务器加载配置文件的时候,就要把配置文件的内容解析出来,放到内存的数据结构中(顺序表/哈希表…)
BlockingQueue
(线程安全的)HashMap
肯定不行,它是线程不安全的。更靠谱的是 Hashtable
,其在一些关键方法上添加了 synchornized
,但这个不好用
synchornized
后来标准库又引入了一个更好的解决方案:ConcurrentHashMap
改进:
Hashtable
是直接在方法上使用 synchornized
,相当于是对 this
加锁,整个哈希表就只有这一把锁,此时,尝试修改两个不同链表上的元素,都会触发锁冲突(Hashtable
底层实现是数组+链表)
ConcurrentHashMap
就是把锁变小了,给每一个链表都发了一个锁
Java
中,任何一个对象都可以直接作为锁对象。本身哈希表中就得有数组,数组的元素都是已经存在的(每个链表的头结点),此时,只要使用数组元素(链表头结点)作为加锁的对象即可在 Java 1.7
及其之前,ConcurrentHashMap
是通过“分段锁”来实现的
Java 8
开始,这里的设定就变成每个链表一把锁了充分使用 CAS 原子操作,减少了一些加锁
synchornized
里面不是刚开始是偏向锁或者轻量级锁,速度很快吗?
synchornized
有可能成为重量级锁,并且是否升级了不可控扩容是一个重量操作
0.75
是负载因子默认的扩容阈值,不是负载因子本体
0.1
,可能是 0.5 可能是 1,可能是 10负载因子:描述了每个桶上面平均有多少个元素
如果太长:
Hash
表的元素都给搬运到(删除/插入)到新的数组上hash
表本身元素非常多,这里的扩容操作就会消耗很长时间hash
表平时都很快,突然间某个操作就变慢了,过一会又快了(表现不稳定,坏事)HashMap
的扩容操作是一把梭哈,在某一次插入元素操作中,整体完成扩容(就会卡顿,耗时)
ConcurrentHashMap
的策略是“化整为零,蚂蚁搬家”
1kw
个元素,此时扩容的时候,每次插入/查找/删除,都会搬运一部分元素(每次搬运 5k
个元素),一共花 2k
次搬完(花的时间更长,但是值得)在扩容过程中,同时存在两份哈希表,一份是新的,一份是旧的