第九章:java 并发容器类了解与使用「建议收藏」

第九章:java 并发容器类了解与使用「建议收藏」第九章:java 并发容器类了解与使用

大家好,又见面了,我是你们的朋友全栈君。

文章转自: https://blog.csdn.net/u012453843/article/details/73824159

并发类容器是专门针对并发设计的,使用ConcurrentHashMap来代替给予散列的传统的HashTable,而且在ConcurrentHashMap中,添加了一些常见复合操作的支持。以及使用了CopyOnWriteArrayList代替Vector,并发的CopyOnWriteArraySet,以及并发的Queue,ConcurrentLinkedQueue和LinkedBlockingQueue,前者是高性能的队列,后者是以阻塞形式的队列,具体实现Queue还有很多,例如ArrayBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。

    下面我们便一一学习下这些并发类容器。


    ConcurrentMap接口有两个重要的实现:ConcurrentHashMap和ConcurrentSkipListMap(支持并发排序功能,弥补ConcurrentHashMap)

    对同步类容器来说比如Vector和HashTable,当有多个线程都要修改容器中的元素的话只能有一个线程进入到容器内进行修改,其它线程都要在外面排队等候,如下图所示。

第九章:java 并发容器类了解与使用「建议收藏」

       ConcurrentHashMap之所以是并发容器是因为它内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的HashTable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。把一个整体分成了16个段(Segment)。也就是最高支持16个线程的并发修改操作。这也是在多线程场景时减小锁的粒度从而降低锁竞争的一种方案。并且代码中大多共享变量使用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。
       我们还是以图来说明,在下图中,我们看到ConcurrentHashMap有16个段,你可以把每个段都相当于一个小的HashTable,也就是说每个小段都是加锁的,并发类容器只是把锁的粒度变小了,原来是一把大锁,现在分成了16个小锁。这样可以有效缓解锁竞争的问题,尽量减少因多个线程争抢一把锁而导致的CPU瞬间爆满的情况。现在有5个线程要修改容器中的元素,假如一、二、三、四4个线程访问的是不同的段,那么它们都可以直接访问它们各自段内的数据并且做修改操作,但是如果几个线程访问同一个小段的话(比如线程四和线程五),那么只能进入一个线程进行修改操作,其它线程只能在外等候。也就是说并发类容器虽然支持并发操作,但是如果并发量非常高的话,也是需要耗费很多时间的,毕竟容器内部只有16个分段,一次只能同时处理16个请求,其余的就要排队等候。不过这相比于同步类容器来说,性能已经好太多了。

第九章:java 并发容器类了解与使用「建议收藏」

          下面我们简单看个ConcurrentHashMap的例子吧,可以看到使用chm.putIfAbsent(“k3”, “vvvv”);方法的话,会先判断map中是否已经添加过名为”k3″的key了。如果已经添加过了,那么就不再添加了。如果没有添加过的话,就会添加。

第九章:java 并发容器类了解与使用「建议收藏」

         将chm.putIfAbsent(“k3”, “vvvv”);中的”k3″换成”k4″,这时再执行main方法,如下图所示。

第九章:java 并发容器类了解与使用「建议收藏」

        下面我们来学习下一个并发类容器:Copy-On-Write容器,Copy-On-Write简称COW,是一种用于程序设计中的优化策略。JDK里的COW容器有两种,分别是
CopyOnWriteArrayList和CopyOnWriteArraySet,COW容器非常有用,可以在非常多的场景中使用到。
         那么什么是CopyOnWrite容器呢?CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往容器添加,而是先将当前容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写在不同的容器。
        光说理论不容易理解,还是上图吧,在下图中,当线程一想要对CopyOnWRite容器进行写操作时,CopyOnWrite容器会复制一份容器出来,然后让线程一去修改容器的副本,而不是修改原容器,这时指针依然指在原容器上,写的过程中如果有多个线程想要读取容器中的数据,那么这些线程会去指针指向的容器中去读取数据。不用加任何锁,这样无论有多少个线程同时访问原容器都是可以的,效率当然就很高了。但是肯定有人会有疑问,就是如果同一时间有多个请求过来要修改容器内的数据,会怎么样?这种情况下,当第一个写请求被容器收到后,容器复制出一个副本出来,线程一去这个副本修改数据,其它线程也要修改容器数据,那就只能在副本容器外排队等候,因为容器的写操作是加锁的。

第九章:java 并发容器类了解与使用「建议收藏」

         当写操作完成后,指针会指向容器的副本,原容器将会被销毁,被垃圾回收器回收。后续再有线程来请求读取容器中的数据的话,就会读取副本这个容器中的数据了。

第九章:java 并发容器类了解与使用「建议收藏」

          下面我们来看下CopyOnWriteArrayList和CopyOnWriteArraySet的一个小例子,如下图所示,可以看到CopyOnWriteArrayList与ArrayList的用法是一样的,
CopyOnWriteArraySet与Set的用法也是一样的。

第九章:java 并发容器类了解与使用「建议收藏」

         代码如下:

[html] 
view plain
 copy

  1. package com.internet.container;  
  2.   
  3. import java.util.Iterator;  
  4. import java.util.concurrent.CopyOnWriteArrayList;  
  5. import java.util.concurrent.CopyOnWriteArraySet;  
  6.   
  7. public class UseCopyOnWrite {  
  8.      
  9.     public static void main(String[] args) {  
  10.         //ArrayList怎样使用,CopyOnWriteArrayList就怎样使用  
  11.         CopyOnWriteArrayList<String> cwal = new CopyOnWriteArrayList<>();  
  12.         cwal.add(“a”);  
  13.         cwal.add(“b”);  
  14.         for(String str : cwal){  
  15.             System.out.println(“str:”+str);  
  16.         }  
  17.         //Set怎样使用,CopyOnWriteArraySet就怎样使用  
  18.         CopyOnWriteArraySet<String> cwas = new CopyOnWriteArraySet<>();  
  19.         cwas.add(“c”);  
  20.         cwas.add(“d”);  
  21.         for (Iterator iterator = cwas.iterator(); iterator.hasNext();) {  
  22.             String string = (String) iterator.next();  
  23.             System.out.println(“string:”+string);  
  24.         }  
  25.     }  
  26. }  

        两种容器add方法都添加了重入锁,如下图所示

第九章:java 并发容器类了解与使用「建议收藏」

          CopyOnWriteArraySet.class类中的代码如下:

第九章:java 并发容器类了解与使用「建议收藏」

         那么CopyOnWrite容器的应用场景是什么呢?
        CopyOnWrite容器特别适用于读多写少的场景,就是基本上都是在读,很少有写的请求的场景。如果是写多读少的话,就不适合,因为CopyOnWrite处理写请求是要复制一份副本出来的,如果容器内的数据量非常大的话,频繁的复制副本将会非常消耗性能,这种场景下CopyOnWrite容器就根本不适用,它甚至没有你在写操作的地方加一把synchronized锁的性能高。所以我们应用CopyOnWrite容器的时候一定要注意使用场景。   

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/106138.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 呼叫中心坐席功能都有哪些?

    呼叫中心坐席功能都有哪些?IPCC是IP呼叫中心(IPCallCenter)的简称,本质上是以IP技术和IP语音为主要应用技术的呼叫中心构建方式,即利用IP传输网来传输与交换语音、图像和文本等信息。(摘自百度百科)

    2022年6月30日
    25
  • 《前端运维》一、Linux基础–01基础命令与vim

    在开始之前,你需要做一些准备工作,去阿里买一台服务器,服务器的具体细节其实并不是十分重要,我也不会在这里一步一步的教大家如何去买一个服务器。百度一下足够了,但是还是要贴一下这系列文章中,我所使用的服务

    2022年3月25日
    37
  • ASP开发中存储过程应用全接触

    ASP开发中存储过程应用全接触

    2021年7月25日
    53
  • Web中常用的Javascript技巧(1)「建议收藏」

    Web中常用的Javascript技巧(1)

    2022年3月8日
    36
  • ORACLE 存储过程死锁[通俗易懂]

    ORACLE 存储过程死锁[通俗易懂]/**问题描述:在编译某个存储过程时,由于没提交或断网或者TEST没停止又重新编译,导致编译存过一直卡死问题分析:存储过程或某张表被锁例如:存储过程p_BonusMID死锁,表现的现象是在编译时无响应。**/–首先使用下面语句查询存过(存储过程)p_BonusMID的进程SELECT*FROMV$DB_OBJECT_CACHEWHEREname=UPPER(‘

    2022年7月17日
    14
  • Java多线程超详解

    Java多线程超详解引言随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。那么话不多说,今天本帅将记录自己线程的学习。线程的相关API//获取当前线程的名字Thread.currentThread().getName()1.start():1.启动当前线程2.调用线程中的run方法2.run():通常需要重写Thread类中的此…

    2022年6月6日
    33

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号