当前位置:   article > 正文

【Java多线程】线程安全的集合类_java 线程安全的集合

java 线程安全的集合

目录

一、多线程环境使用 ArrayList

二、多线程环境使用队列

三、多线程环境使用哈希表

1. HashTable

2. ConcurrentHashMap 


一、多线程环境使用 ArrayList

在我们之前使用的集合类中,大部分都是线程不安全的。

Vector,Stack,HashTable是线程安全的,之前单线程环境就几乎不使用,即使在多线程环境在也不建议使用。因为这些集合类只是在内部自带了 synchronized,但不能保证有锁就一定线程安全。

对于使用 ArrayList,有以下几种方案:

1. 自己手动加锁(synchronized)

2. 使用 Collections.synchronizedList(new ArrayList) 创建列表

  • 对于这个列表的所有操作(例如添加、删除、修改元素等)都会在方法级别进行同步,从而保证多个线程同时访问时的安全性。

3. 使用 CopyOnWriteArrayList 

  • 当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后往新的容器里添加元素。并发读的时候,读取的是旧容器内的元素,这样就不会有读到中间值的情况发生(修改了部分数据),也不需要加锁。
  • 添加完元素之后,再将原容器的引用指向新的容器。因此,这样的做法是一种读写分离的思想,读和写使用不同的容器。
  • 优点:读多写少的场景下,性能很高,不需要加锁。
  • 缺点:占用内存较多,新写的数据不能第一时间被读取到。

二、多线程环境使用队列

使用前面案例讲过的标准库中的阻塞队列即可。

  1. ArrayBlockingQueue:基于数组实现的有界阻塞队列,必须指定容量。
  2. LinkedBlockingQueue:基于链表实现的可选有界或无界阻塞队列。如果不指定容量,则为无界队列。
  3. PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  4. DelayQueue:用于延迟元素的无界阻塞队列,其中每个元素都有一个过期时间,只有在过期时才能被取出。
  5. SynchronousQueue:一个特殊的阻塞队列,每个插入操作必须等待一个相应的删除操作,反之亦然。
  6. TransferQueue:最多只包含一个元素的阻塞队列。

三、多线程环境使用哈希表

平时经常使用的 HashMap,本身就是线程不安全的。

在多线程环境下可以使用:

  • HashTable
  • ConcurrentHashMap

1. HashTable

前面说过,HashTable是不推荐使用的。

HashTable 只是简单粗暴的把关键方法加上了 synchronized 关键字。

这相当于直接针对 HashTable 对象本身加锁。

  • 如果多线程访问同一个 HashTable 就会直接造成锁冲突(访问任何数据都会有锁冲突).
  • size 属性也是通过 synchronized 来控制同步,是比较慢的.
  • 一旦触发扩容,就由该线程完成整个扩容过程。这个过程会涉及到大量元素拷贝,效率会非常低(相比put/get等操作慢很多,在业务场景下就会有明显卡顿)。

而标准库中提供了更好的代替,ConcurrentHashMap,对上述问题做出了改进和优化.

2. ConcurrentHashMap 

以Java1.8为例

1. 使用"锁桶"的方式,来代替"一把全局锁",有效降低锁冲突的概率

  • 我们知道,哈希表中的每个元素是一个哈希桶(链表),只有当多个线程针对相同的哈希桶进行操作,才会涉及到锁冲突(用每个链表的头结点作为锁对象),大大降低了锁冲突。

2. 充分利用CAS特性

  • 比如 hash 表的 size 属性,即使插入的元素是不同链表上的元素,也会涉及到多线程修改同一个变量。ConcurrentHashMap 引入CAS,通过 CAS 的方式,来修改 size,避免了加锁操作。

3. 优化了扩容方式:化整为零

  • ConcurrentHashMap 会在扩容的时候,多搞一份空间,一份是扩容之前的空间,一份是扩容之后的空间。
  • 相比于普通的HashMap,ConcurrentHashMap 不是一次性直接扩容完成,而是接下来每次有线程对 hash 表操作的时候,都把一部分数据从 旧空间 搬运到 新空间。等搬完所有元素之后,才把老空间删掉,使用新空间的 hash 表。
  • 在搬的过程中,插入=>只往新的 hash 表插入,删除=>新的旧的都删除,查找=>同时查找两个 hash 表。

ConcurrentHashMap 在Java1.7采用的是锁分段技术,简单来说就是把若干个哈希桶分成一个段(Segment),针对每个分段进行加锁。目的也是为了降低锁竞争的概率,只有当多个线程访问的数据恰好在一个段上的时候,才触发锁竞争。Java1.8则进行的就是上述"锁桶"的优化。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/670955
推荐阅读
相关标签
  

闽ICP备14008679号