线程不安全的HashMap

因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

效率低下的HashTable容器

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。

因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。

如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

ConcurrentHashMap的锁分段技术

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁。

那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。

首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。

Segment是一种重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,

HashEntry则用于存储键值对数据。

一个ConcurrentHashMap里包含一个Segment数组,  每个Segment守护者一个HashEntry数组里的元素,

当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

 

ConcurrentHashMap使用方法:

虽然ConcurrentHashMap是线程安全的,如果你只调用get(),或只调用put()时,ConcurrentHashMap是线程安全的。

但是,在你调用完get后,调用put之前,如果有另外一个线程调用了put(name, x),你再去执行put(name,x),就很可能把前面的操作结果覆盖掉了。

所以,即使在线程安全的情况下,你还是有可能违反原子操作的规则。

当然可以用 锁 解决这个问题,但性能受到很大影响。

另一种思路,也可以使用ConcurrentMap定义的循环CAS方法:

    1. putIfAbsent(K key, V value)
    2. 如果key对应的value不存在,则put进去,返回null。否则不put,返回已存在的value
    3. boolean remove(Object key, Object value)
    4. 如果key对应的值是value,则移除K-V,返回true。否则不移除,返回false。
    5. boolean replace(K key, V oldValue, V newValue)  //循环CAS算法
    6. 如果key对应的当前值是oldValue,则替换为newValue,返回true。否则不替换,返回false。
  1. public static void demo1() {
  2. final Map<String, Integer> count = new ConcurrentHashMap<>();
  3. Runnable task = new Runnable() {
  4. @Override
  5. public void run() {
  6. Integer oldValue, newValue;
  7. for (int i = 0; i < 5; i++) {
  8. while (true) {
  9. oldValue = count.get("a");
  10. if (null == oldValue) {
  11. newValue = 1;
  12. if (count.putIfAbsent("a", newValue) == null) {
  13. break;
  14. }
  15. } else {
  16. newValue = oldValue + 1;
  17. if (count.replace("a", oldValue, newValue)) {
  18. break;
  19. }
  20. }
  21. }
  22. }
  23. }
  24. };
  25. new Thread(task).start();
  26. new Thread(task).start();
  27. }

还一种思路,也可以使用Atomic原子操作方法(本质也是循环CAS):

    1. public static void demo1() {
    2. final Map<String, AtomicInteger> count = new ConcurrentHashMap<>();
    3. Runnable task = new Runnable() {
    4. @Override
    5. public void run() {
    6. AtomicInteger oldValue;
    7. for (int i = 0; i < 5; i++) {
    8. oldValue = count.get("a");
    9. if (null == oldValue) {
    10. AtomicInteger zeroValue = new AtomicInteger(0);
    11. oldValue = count.putIfAbsent("a", zeroValue);  //赋予初始值:0
    12. if (null == oldValue) {
    13. oldValue = zeroValue;
    14. }
    15. }
    16. oldValue.incrementAndGet();  //原子循环CAS自增操作
    17. }
    18. }
    19. };
    20. new Thread(task).start();
    21. new Thread(task).start();

深入理解java:2.3.3. 并发编程concurrent包 之容器ConcurrentHashMap的更多相关文章

  1. 深入理解java:2.3.4. 并发编程concurrent包 之容器ConcurrentLinkedQueue(非阻塞的并发队列---循环CAS)

    1.    引言 在并发编程中我们有时候需要使用线程安全的队列. 如果我们要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法. 使用阻塞算法的队列可以用一个锁(入队和出 ...

  2. 深入理解java:2.3.5. 并发编程concurrent包 之容器BlockingQueue(阻塞队列)

    1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空. 当队列满时,存储元素的线程会等待队列 ...

  3. Python并发编程-concurrent包

    Python并发编程-concurrent包 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.concurrent.futures包概述 3.2版本引入的模块. 异步并行任务编程 ...

  4. 深入理解java:2.3. 并发编程 java.util.concurrent包

    JUC java.util.concurrent包, 这个包是从JDK1.5开始引入的,在此之前,这个包独立存在着,它是由Doug Lea开发的,名字叫backport-util-concurrent ...

  5. 深入理解java:2.3.6. 并发编程concurrent包 之管理类---线程池

    我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁 ...

  6. 深入理解java:2.3.2. 并发编程concurrent包 之重入锁/读写锁/条件锁

    重入锁 Java中的重入锁(即ReentrantLock)   与JVM内置锁(即synchronized)一样,是一种排它锁. ReentrantLock提供了多样化的同步,比如有时间限制的同步(定 ...

  7. 深入理解java:2.3.1. 并发编程concurrent包 之Atomic原子操作(循环CAS)

    java中,可能有一些场景,操作非常简单,但是容易存在并发问题,比如i++, 此时,如果依赖锁机制,可能带来性能损耗等问题, 于是,如何更加简单的实现原子性操作,就成为java中需要面对的一个问题. ...

  8. 并发编程从零开始(八)-ConcurrentHashMap

    并发编程从零开始(八)-ConcurrentHashMap 5.5 ConcurrentHashMap HashMap通常的实现方式是"数组+链表",这种方式被称为"拉链 ...

  9. Java 面试宝典!并发编程 71 道题及答案全送上!

    金九银十跳槽季已经开始,作为 Java 开发者你开始刷面试题了吗?别急,我整理了71道并发相关的面试题,看这一文就够了! 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程( ...

随机推荐

  1. C风格函数

    很多C风格的函数用起来非常舒适,例如: if(access(sPath, 0) == 0){ ://检测文件是否存在 } 用来测试文件存在与否,以及读写权限. 而他有宽字节版与ascii码版 宽版 _ ...

  2. 【java工具类】上传文件

    /**上传文件 * @param file 文件 * @param filePath 上传文件路径,不包含文件名 * @param fileName 新的文件名 * @return 返回一个路径名 * ...

  3. TTTTTTTTTTTTTTTTT POJ 2226 草地覆木板 二分匹配 建图

    Muddy Fields Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 9754   Accepted: 3618 Desc ...

  4. TTTTTTTTTTTTT 树的直径 Codeforces Beta Round #14 (Div. 2) D. Two Paths

    tiyi:给你n个节点和n-1条边(无环),求在这个图中找到 两条路径,两路径不相交,求能找的两条路径的长度的乘积最大值: #include <iostream> #include < ...

  5. CentOS7 服务器上如何安装python3

    1.官网下载python3的源码包 网址:https://www.python.org/ 进去之后点击导航栏的Downloads,也可以鼠标放到Downloads上弹出菜单选择Source code, ...

  6. 【Python】学习笔记一:Hello world

    前言 在我看来,无论我们学习什么语言第一个学习的估计都是Hello world,那么接下来就从Hello world说起! 编写代码 我在本机上已经安装了pycharm,所以我所编辑的代码都是在pyc ...

  7. App可视化埋点技术原理大揭秘

    一.背景 运营者能够对用户行为进行分析的前提,是对大量数据的掌握.在以往,这个数据通常是由开发者在控件点击.页面等事件中,一行行地编写埋点代码来完成数据收集的.然而传统的操作模式每当升级改版时,开发和 ...

  8. H5视频活动踩坑

    最近做了一些嵌入视频的活动,积累了点视频方面的经验,下面记录下别人和自己踩过的坑以及相应的解决方案.1.碰到问题和解决方案1.1.ios 网页中播放视频默认全屏(点击视频会弹出播放器进行全屏播放).解 ...

  9. C++入门经典-例2.6-简单用cout输出字符

    1:代码如下: // 2.6.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> using ...

  10. 剑指offer:把一个支付算转化为整数

    1:首先,根据课本上的程序,是这样的: #include "stdafx.h" #include "iostream" using namespace std; ...