线程安全的集合类

原来的集合类, 大部分都不是线程安全的.

        Vector, Stack, HashTable, 是线程安全的,关键方法带有 synchronized  ( 不建议用,类似于无脑加锁 ), 其他的集合类不是线程安全的 .

多线程环境使用 ArrayList

1) 自己加锁, synchronized 或者 ReentrantLock
2) Collections.synchronizedList(new ArrayList),这里会提供一些 ArrayList 相关的方法,同时是带锁的。
3) 使用 CopyOnWriteArrayList(COW,也叫做“写时拷贝”),针对 ArrayList 进行读操作,不做任何额外的工作;如果进行写操作,则拷贝一份新的 ArrayList,针对新的进行修改,如果修改过程中有读操作,就继续读旧的这份数据,当修改完毕了,就使用新的替换旧的。
        例如服务器程序的配置文件,修改配置可能需要重启服务器,但是每次重启服务器耗费很长时间,重启多台耗费的时间就更多,因此,很多服务器就提供了“热加载”(reload)这样的功能。热加载的实现使用到了 写时拷贝 的思路:新的配置放到新的对象中,加载过程里,请求仍基于旧配置进行工作,当新的对象加载完毕,就用新的代替旧的(替换完成,旧的就可以释放了)。
优点 :
在读多写少的场景下 , 性能很高 , 不需要加锁竞争 .
缺点 :
1. 占用内存较多,要求这个 ArrayList 不能太大。
2. 新写的数据不能被第一时间读取到。

多线程环境使用队列

1) ArrayBlockingQueue
        基于数组实现的阻塞队列
2) LinkedBlockingQueue
        基于链表实现的阻塞队列
3) PriorityBlockingQueue
        基于堆实现的带优先级的阻塞队列
4) TransferQueue
        最多只包含一个元素的阻塞队列

多线程环境使用哈希表 ¥

HashMap 本身是不线程安全的 .
在多线程环境下使用哈希表可以使用:
        Hashtable 是线程安全的
        ConcurrentHashMap 是更优化的线程安全的哈希表

          此时,元素 1 和 2 在同一个链表上,如果线程 A 修改元素 1,线程 B 修改元素 2,那么就会有线程安全,就需要加锁。如果是线程 A 修改元素 3,线程 B 修改元素 4,就不会有线程问题,也就不需要加锁

Hashtable

        只是简单的把关键方法加上了 synchronized 关键字,等于给 this 加锁, 这相当于直接针对Hashtable 对象本身加锁 .

       这时候就是加了一个大锁,锁冲突的概率太大了,任何两个元素的操作都会有锁冲突,即使是在不同的链表上。

ConcurrentHashMap

        相比于 Hashtable 做出了一系列的改进和优化 . Java1.8 为例。ConcurrentHashMap 的做法是每个链表都有各自的锁,就是以每个链表的头节点作为锁对象,把锁的粒度降低了。

        针对 1 和 2 这个情况,是针对同一把锁进行加锁,会有锁竞争;针对 3 和 4 这个情况,是针对不同对象进行加锁,不会有锁竞争,没有阻塞等待。

ConcurrentHashMap 比 HashTable 好在哪里
        1. ConcurrentHashMap 相比于 HashTable 大大缩小了锁冲突的概率
        2. ConcurrentHashMap 做了一个比较激进的操作:针对读操作不加锁,只针对写操作加锁。这就意味着读和写之间就没有冲突了(出现脏读),因此ConcurrentHashMap 采用了 volatile + 一些原子的写操作来保证读和写之间没有脏读问题。
        3. ConcurrentHashMap 内部充分使用了 CAS,通过这个也来进一步减少加锁操作的数目,比如 size++ 。
        4. 针对扩容:
        HashMap / HashTable 是创建一个更大的数组空间,把旧的数组上的链表中的每个元素搬运到新的数组上,这个操作会在某次 put 的时候触发。如果某次的数据特别多,这个操作就会比较耗时就会卡顿。
        ConcurrentHashMap 采取的是“化整为零”的方式,即每次搬运一小部分的元素。创建新的数组,旧的数组也会保留,每次进行 put 的时候往新数组上添加元素,同时把一小部分旧的元素搬运到新的数组上,每次 get 的时候则新旧数组都进行查询,每次 remove 的时候把元素删除即可。一段时间后旧的元素搬运完了,此时旧数组就可以释放了。

相关面试题

1) ConcurrentHashMap 的读是否要加锁,为什么 ?
读操作没有加锁 . 目的是为了进一步降低锁冲突的概率 . 为了保证读到刚修改的数据 , 搭配了
volatile 关键字 .
2) 介绍下 ConcurrentHashMap 的锁分段技术 ?
这个是 Java1.7 中采取的技术 . Java1.8 中已经不再使用了 . 简单的说就是把若干个哈希桶分成一个
" " (Segment), 针对每个段分别加锁 .
目的也是为了降低锁竞争的概率 . 当两个线程访问的数据恰好在同一个段上的时候 , 才触发锁竞争 .
3) ConcurrentHashMap jdk1.8 做了哪些优化?
取消了分段锁 , 直接给每个哈希桶 ( 每个链表 ) 分配了一个锁 ( 就是以每个链表的头结点对象作为锁对
).
将原来 数组 + 链表 的实现方式改进成 数组 + 链表 / 红黑树 的方式 . 当链表较长的时候 ( 大于等于
8 个元素 ) 就转换成红黑树 .
4) Hashtable HashMap ConcurrentHashMap 之间的区别 ?
HashMap: 线程不安全 . key 允许为 null
Hashtable: 线程安全 . 使用 synchronized Hashtable 对象 , 效率较低 . key 不允许为 null.
ConcurrentHashMap: 线程安全 . 使用 synchronized 锁每个链表头结点 , 锁冲突概率低 , 充分利用
CAS 机制 . 优化了扩容方式 . key 不允许为 null
  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
除了提供高性能的线程安全集合类java的并发包还提供了以下功能: 1. 并发执行框架:java.util.concurrent包中的Executor框架提供了一种使用线程池的方式来执行并发任务。它将任务的提交与任务的执行解耦,可以通过线程池来管理和复用线程,并提供了任务执行的调度和控制的功能。 2. 并发工具类:并发包中还提供了一些工具类,如CountDownLatch、CyclicBarrier、Semaphore等,用于帮助实现更加复杂的并发控制。这些工具类可以用于同步多个线程的执行,控制线程的并发数量,并在一些条件满足后触发线程的执行等功能。 3. 原子操作类:并发包中提供了一系列原子操作类,如AtomicInteger、AtomicLong等,用于在多线程环境下对变量进行原子操作。这些类通过使用CAS(Compare and Swap)操作来保证变量的原子性,避免了使用synchronized关键字进行同步操作带来的性能开销。 4. 并发线程安全工具类:并发包中还提供了一些线程安全的辅助类,如CopyOnWriteArrayList、ConcurrentHashMap等,用于替代传统的非线程安全集合类。这些类通过使用一些特定的并发算法来保证多线程环境下的线程安全性,能够在并发读写的情况下提供较好的性能。 总之,java的并发包不仅提供了高性能的线程安全集合类,还提供了一些并发执行框架、并发工具类、原子操作类以及线程安全工具类,用于帮助开发者在多线程环境下更加方便、高效地编写并发代码。这些功能的引入使得开发者能够更好地处理并发程序的编写与调试,提高了程序的性能和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值