🔍 开发者资源导航 🔍 |
---|
🏷️ 博客主页: 个人主页 |
📚 专栏订阅: JavaEE全栈专栏 |
多线程安全使用ArrayList
手动加锁
日常中最常用的方法,使用synchronized进行加锁,把代码打包成一份,使其成为一个“原子”操作。
手动加锁:可以只锁必要的部分,减少竞争。适用于高并发场景。
使用Collections套壳
Collections.synchronizedList() 返回一个 线程安全的 List 包装器,所有对 List 的操作(如 add(), remove(), get())都会自动加锁,确保多线程安全。
List<Integer> arr = Collections.synchronizedList(new ArrayList<Integer>());
缺点:
- synchronizedList 只保证单个操作是原子的,但 遍历(iteration)仍然需要手动加锁。
- 锁的粒度较大,导致:高并发写操作时,线程会频繁竞争同一把锁,造成阻塞(contention)。读操作也会被阻塞(即使读操作本身不修改数据)。
适用于适用于低并发(少量写操作)。
CopyOnWriteArrayList
CopyOnWriteArrayList在操作时不去加锁,它在进行读写操作时使用了一种常见的思想方法:写时拷贝。
他在修改时,先创建一个相同的数组,对这个新的数组进行修改操作,然后改变原数组的引用。
虽然复制过程不是原子的,但是由于提供了旧版本的数组,不影响其他线程读取。
优点:
没有加锁~不会产生阻塞。
缺点:
- 如果数组很大,效率会非常大。
- 多个线程同时修改,也容易出现问题,可以会丢失修改数据。
- 只适用于特定场景,例如:服务器进行重新加载配置的时候。
多线程安全使用哈希表
Hashtable
Hashtable相比于Hashmap对各种public方法都加了synchronized,可以保证线程安全。
缺点:对整个哈希表进行了加锁,在访问时很容易造成竞争,造成效率降低。
ConcurrentHashMap
基于哈希表进行的多线程优化,他按照桶级别进行加锁,而不是加一个全局锁,有效降低锁冲突的概率。
优化点一
在Hashtable中任意的两个线程访问不同的两个元素都会造成竞争,但是实际上,处于两个不同链表上的两个元素是不会涉及线程安全问题的。
因此ConcurrentHashMap对每一个节点都分配一个锁,可以减少修改不同变量时的锁竞争。
那么这样做会增加它占的空间吗?在Java中每一个对象都可以作为锁对象,因此我们完全可以让每一个链表的头结点当做锁对象,因此并不会增加空间。
优化点二
上述优化点虽然可以减少锁竞争,但是却无法保证size()方法的线程安全,因为它的锁不是全局锁了,而是分别加锁。因此ConcurrentHashMap的size改用了原子类进行存储。
优化点三
ConcurrentHashMap针对扩容操作也进行了优化,在把旧哈希的元素搬运到新的哈希过程中,如果元素很多,耗时就会很长。
ConcurrentHashMap针对扩容的操作将其化整为零,既然一口气搬运比较好使,那就一次只搬一部分,这样的操作可以将扩容的耗时平均分担开,而不会造成某次的操作时间过长。
感谢各位的观看Thanks♪(・ω・)ノ,如果觉得满意的话留个关注再走吧。