线程不安全的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. 利用docker 部署项目

    docker_tomcat_jdk 7.0 1.6 app admin && api 1.yum install docker 2.service docker start 3.创建文 ...

  2. Centos7——docker入门(笔记)

    docker 入门(笔记) 一.Docker是什么? 官方原话: Docker provides a way to run applications securely isolated in a co ...

  3. 2019春Python程序设计测试(20190611--20190611)

    1-1 Python使用缩进来体现代码之间的逻辑关系. (2分) T         F 1-1答案正确(2 分) 1-2 为了输出",可以使用如下语句print(""& ...

  4. 千万级别数据量mysql优化策略

    表结构优化 1.  使用独立表空间 独立表空间指的是innodb表的一种数据结构 独占表空间:  每一个表都将会生成以独立的文件方式来进行存储,每一个表都有一个.frm表描述文件,还有一个.ibd文件 ...

  5. Eclipse 导入逆向工程

    相关配置 生成 生产前 生成后

  6. CF 680D 堆塔

    D. Bear and Tower of Cubes time limit per test 2 seconds memory limit per test 256 megabytes input s ...

  7. 多态:JVM是如何进行方法调用的

    在我们平时的工作学习中写java代码时,如果我们在同一个类中定义了两个方法名和参数类型都相同的方法时,编译器会直接报错给我们.还有在代码运行的时候,如果子类定义了一个与父类完全相同的方法的时候,父类的 ...

  8. Spring boot之配置server信息

    知识点: 1.修改端口号 2.修改context-path 3.其它配置说明 配置端口号: Spring boot 默认端口是8080, 如果想要进行更改的话, 只需要修改applicatoin.pr ...

  9. CentOS 6.5上的Tomcat启动报错问题

    最近在搭建虚拟机环境,装的是CentOSQL 6.5版本,然后装的OpenJDK1.7,在Apache下载了一个纯净的Tomcat放到虚拟机上启动报错了: 这里有两个错误: 1.第一个错误,APR的问 ...

  10. Idea如何生成JPA的相关model,以及运行JPA项目的时候启动错误

    1.如何生成JPAmodel 按照顺序执行下面的步骤 为指定的项目添加JPA的配置,这样之后生成的model就会在指定的项目内 选择JPA之后默认不用操作直接添加 没有Persistence的可以在w ...