Java多线程工具包java.util.concurrent---ConcurrentHashMap

本文对比了ConcurrentHashMap、HashMap及HashTable的特点与应用场景,重点讲解了ConcurrentHashMap如何通过分段锁提高并发性能,并给出示例代码展示其在数据缓存中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考以下博客
http://blog.youkuaiyun.com/xiaohui127/article/details/11928865
http://blog.youkuaiyun.com/xuefeng0707/article/details/40797085
http://www.importnew.com/21388.html
本文没有具体探讨ConcurrentHashMap、HashMap、HashTable的实现,只是对其做了比较和总结,在工作中该如何使用,避免不必要的错误


什么是ConcurrentHashMap

ConcurrentHashMap 和 java.util.HashTable 类很相似,但 ConcurrentHashMap 能够提供比 HashTable 更好的并发性能。在你从中读取对象的时候 ConcurrentHashMap 并不会把整个 Map 锁住。此外,在你向其中写入对象的时候,ConcurrentHashMap 也不会锁住整个 Map。它的内部只是把 Map 中正在被写入的部分进行锁定。
另外一个不同点是,在被遍历的时候,即使是 ConcurrentHashMap 被改动,它也不会抛 ConcurrentModificationException。

有什么特点

1、允许并发的读和线程安全的更新操作
2、在执行写操作时,只锁住部分的Map

  • 这里说明以下同为线程安全的HashTable,主要区别在于
    ConcurrentHashMap是分段锁(Segmentation),而HashTable是锁住整个map,在并发性能上ConcurrentHashMap优于HashTable

3、并发的更新是通过内部根据并发级别将Map分割成小部分实现的
4、高的并发级别会造成时间和空间的浪费,低的并发级别在写线程多时会引起线程间的竞争
5、所有操作都是线程安全
6、返回的迭代器是弱一致性,fail-safe并且不会抛出ConcurrentModificationException异常
7、不允许null的键值

  • key和value都不允许为null

8、可以使用ConcurrentHashMap代替HashTable,但要记住ConcurrentHashMap不会锁住整个Map

HashMap造成cpu100%的问题

Java HashMap的死循环
这篇文章详细介绍了多线程下HashMap在rehash过程中造成死循环,引起cpu100%的过程和原因。
简单来说:由于rehash的机制,多线程下使hashmap内部形成了环形链表.

示例

这里简单写一个利用线程安全的特点ConcurrentHashMap作为数据缓存。
ConcurrentHashMap比较适合多读少写的场景

class AddressData {
    private static ConcurrentMap<String, Object> map = new ConcurrentHashMap<>();

    public static Object getData(String param) {
        Object result = null;
        try {
            result = map.get(param);
            if (result == null) {
                HttpHelper helper = new HttpHelper();
                // 如果key已经存在,那么返回的值就是key对应的value
                // 如果key不存在,那么返回的值为null
                map.putIfAbsent(param, helper.getProviceCityArea());
                result = map.get(param);
            }

        } catch (Exception e) {
            // TODO 异常管理
        }
        return result;
    }
}

class HttpHelper {

    public Address getProviceCityArea() {
        Address address = new Address();
        address.setProviceCode("1");
        address.setProviceCode("四川省");
        List<City> cites = new ArrayList<>();
        City city = new City();
        city.setCityCode("10");
        city.setCityName("成都市");
        List<Area> areas = new ArrayList<>();
        Area area = new Area();
        area.setAreaCode("101");
        area.setAreaName("锦江区");
        areas.add(area);
        city.setAreas(areas);
        cites.add(city);
        address.setCites(cites);
        return address;
    }
}

class Address {
    private String proviceCode;
    private String proviceName;
    private List<City> cites;

    public String getProviceCode() {
        return proviceCode;
    }

    public void setProviceCode(String proviceCode) {
        this.proviceCode = proviceCode;
    }

    public String getProviceName() {
        return proviceName;
    }

    public void setProviceName(String proviceName) {
        this.proviceName = proviceName;
    }

    public List<City> getCites() {
        return cites;
    }

    public void setCites(List<City> cites) {
        this.cites = cites;
    }

    @Override
    public String toString() {
        return "Address [proviceCode=" + proviceCode + ", proviceName=" + proviceName + ", cites=" + cites.toString()
                + "]";
    }

}

class City {
    private String cityCode;
    private String cityName;
    private List<Area> areas;

    public String getCityCode() {
        return cityCode;
    }

    public void setCityCode(String cityCode) {
        this.cityCode = cityCode;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public List<Area> getAreas() {
        return areas;
    }

    public void setAreas(List<Area> areas) {
        this.areas = areas;
    }

    @Override
    public String toString() {
        return "City [cityCode=" + cityCode + ", cityName=" + cityName + ", areas=" + areas.toString() + "]";
    }

}

class Area {
    private String areaCode;
    private String areaName;

    public String getAreaCode() {
        return areaCode;
    }

    public void setAreaCode(String areaCode) {
        this.areaCode = areaCode;
    }

    public String getAreaName() {
        return areaName;
    }

    public void setAreaName(String areaName) {
        this.areaName = areaName;
    }

    @Override
    public String toString() {
        return "Area [areaCode=" + areaCode + ", areaName=" + areaName + "]";
    }

}

探讨示例

前面提到的concurrentHashMap线程安全,主要是利用了分段锁来实现,保证在每次访问只允许一个线程修改哈希表的映射关系
但是是否具有原子性?
ConcurrentMap能够保证每一次调用(例如一次putIfAbsent)都是原子操作,不受多线程影响,但并不保证多次调用之间也是原子操作

package com.yvan.concurrentMap;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
/**
 * 
 * @author yvan
 *
 */
public class AppMain {

    private static ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<Integer, Integer>(); 
    public static void main(String[] args) throws InterruptedException {  
        CountDownLatch countDownLatch = new CountDownLatch(3);
        map.put(3, 1);
        new Thread("Thread1"){  
            @Override  
            public void run() {  
                System.out.println(Thread.currentThread().getName()+"s=="+map.get(3));
                int temp = map.get(3)+1;
                map.put(3, temp);
                System.out.println(Thread.currentThread().getName()+"e=="+map.get(3));
                countDownLatch.countDown();
            }  
        }.start();  

        new Thread("Thread2"){  
            @Override  
            public void run() {  
                System.out.println(Thread.currentThread().getName()+"s=="+map.get(3));
                int temp = map.get(3)+1;
                map.put(3, temp);
                System.out.println(Thread.currentThread().getName()+"e=="+map.get(3));
                countDownLatch.countDown();
            }  
        }.start();  

        new Thread("Thread3"){  
            @Override  
            public void run() {  
                System.out.println(Thread.currentThread().getName()+"s=="+map.get(3));
                int temp = map.get(3)+1;
                map.put(3, temp);
                System.out.println(Thread.currentThread().getName()+"e=="+map.get(3));
                countDownLatch.countDown();
            }  
        }.start();  
        countDownLatch.await();
        System.out.println(map.get(3));

    }  
}

结果

Thread1s==1
Thread1e==2
Thread2s==1
Thread2e==3
Thread3s==3
Thread3e==4
4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值