随笔 - 1357  文章 - 0  评论 - 1104  阅读 - 1941万

分段锁——ConcurrentHashMap

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

2、效率低下的HashTable容器
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

3、ConcurrentHashMap分段锁技术
ConcurrentHashMap是Java 5中支持高并发、高吞吐量的线程安全HashMap实现。

我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

4、用法
模拟信息的发送和接收,场景是这样的:
服务器向客户端发送信息,要保证信息100%的发送给客户端,那么发给客户端之后,客户端返回一个消息告诉服务器,已经收到。当服务器一直没有收到客户端返回的消息,那么服务器会一直发送这个信息,直到客户端接收并确认该信息,这时候再删除重复发送的这个信息。

为了模拟该场景,这里写两个线程,一个是发送线程,一个是接收线程,把要发送的信息保存到线程安全的对象里面,防止发生线程安全问题,这里采用ConcurrentHashMap。

a、SendThread:信息重新发送类

复制代码
package com.lynch.lock;

import java.util.Map.Entry;

/**   
 *
 *
 * @author Lynch 
 */
public class SendThread extends Thread {
    @Override
    public void run() {
        try {
            sleep(6000);
            while (ConcurrentHashMapTest.pushMessage.size() > 0) {
                for (Entry<Integer, String> hashMap : ConcurrentHashMapTest.pushMessage.entrySet()) {
                    System.out.println("消息id:" + hashMap.getKey()+ "未发送成功,在此重发:" + hashMap.getValue());
                }
                
                sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码

b、ReceiveThread:信息接收类

复制代码
package com.lynch.lock;

import java.util.Map.Entry;

/**
 * 接收发送过来的信息,并从内存中删除
 * 
 * @author Administrator
 *
 */
public class ReceiveThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 100000; i++) {
                sleep(2000);
                for (Entry<Integer, String> map : ConcurrentHashMapTest.pushMessage.entrySet()) {
                    if (map.getKey() == i) {
                        System.out.println("成功收到id为:" + map.getKey() + "返回的信息,删除该元素");
                        ConcurrentHashMapTest.pushMessage.remove(map.getKey());
                    }
                }
                System.out.println("内存对象中的元素数量为:" + ConcurrentHashMapTest.pushMessage.size());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码

 

c、ConcurrentHashMapTest:主类入口

复制代码
package com.lynch.lock;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 模拟信息的发送和接收
 *
 * @author Lynch
 */
public class ConcurrentHashMapTest {
    public static ConcurrentHashMap<Integer, String> pushMessage = new ConcurrentHashMap<Integer, String>();

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            pushMessage.put(i, "该消息是id为" + i + "的消息");
        }

        Thread sendThread = new SendThread();
        Thread receiveThread = new ReceiveThread();
        sendThread.start();
        receiveThread.start();
        for (int i = 5; i < 10; i++) {
            pushMessage.put(i, "该消息是id为" + i + "的消息");
        }
    }
}
复制代码

这样两个线程可以轮流的进行各自的事情,并且不会造成数据安全的问题。用这种方式,再结合Androidpn的推送机制,会更加符合实际生产中的应用。

 

posted on   Ruthless  阅读(11295)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示