`

java线程及ComcurrentHashMap

阅读更多
http://blog.csdn.net/dimly113/article/details/6461120
一.Java并发基础

当一个对象或变量可以被多个线程共享的时候,就有可能使得程序的逻辑出现问题。
在一个对象中有一个变量i=0,有两个线程A,B都想对i加 1,这个时候便有问题显现出来,关键就是对i加1的这个过程不是原子操作。要想对i进行递增,第一步就是获取i的值,当A获取i的值为0,在A将新的值写入A之前,B也获取了A的值0,然后A写入,i变成1,然后B也写入i,i这个时候依然是1.
当然java的内存模型没有上面这么简单,在Java Memory Model中,Memory分为两类,

main memory和working memory,main memory为所有线程共享,working memory中存放的是线程所需要的变量的拷贝(线程要对main memory中的内容进行操作的话,首先需要拷贝到自己的working memory,一般为了速度,working memory一般是在cpu的cache中的)。volatile的变量在被操作的时候不会产生working memory的拷贝,而是直接操作main memory,当然volatile虽然解决了变量的可见性问题,但没有解决变量操作的原子性的问题,这个还需要synchronized或者CAS相关操作配合进行。

多线程中几个重要的概念:


可见性



也就说假设一个对象中有一个变量i,那么i是保存在main memory中的,当某一个线程要操作i的时候,首先需要从main memory中将i 加载到这个线程的working memory中,这个时候working memory中就有了一个i的拷贝,这个时候此线程对i的修改都在其working memory中,直到其将i从working memory写回到main memory中,新的i的值才能被其他线程所读取。从某个意义上说,可见性保证了各个线程的working memory的数据的一致性。
可见性遵循下面一些规则:

    * 当一个线程运行结束的时候,所有写的变量都会被flush回main memory中。
    * 当一个线程第一次读取某个变量的时候,会从main memory中读取最新的。
    * volatile的变量会被立刻写到main memory中的,在jsr133中,对volatile的语义进行增强,后面会提到
    * 当一个线程释放锁后,所有的变量的变化都会flush到main memory中,然后一个使用了这个相同的同步锁的进程,将会重新加载所有的使用到的变量,这样就保证了可见性。


原子性



还拿上面的例子来说,原子性就是当某一个线程修改i的值的时候,从取出i到将新的i的值写给i之间不能有其他线程对i进行任何操作。也就是说保证某个线程对i的操作是原子性的,这样就可以避免数据脏读。
通过锁机制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保证操作的原子性。


有序性



假设在main memory中存在两个变量i和j,初始值都为0,在某个线程A的代码中依次对i和j进行自增操作(i,j的操作不相互依赖),
1
2

i++;
j++;

由于,所以i,j修改操作的顺序可能会被重新排序。那么修改后的ij写到main memory中的时候,顺序可能就不是按照i,j的顺序了,这就是所谓的reordering,在单线程的情况下,当线程A运行结束的后i,j的值都加1了,在线程自己看来就好像是线程按照代码的顺序进行了运行(这些操作都是基于as-if- serial语义的),即使在实际运行过程中,i,j的自增可能被重新排序了,当然计算机也不能帮你乱排序,存在上下逻辑关联的运行顺序肯定还是不会变的。但是在多线程环境下,问题就不一样了,比如另一个线程B的代码如下
1
2
3

if(j==1) {
    System.out.println(i);
}

按照我们的思维方式,当j为1的时候那么i肯定也是1,因为代码中i在j之前就自增了,但实际的情况有可能当j为1的时候i还是为0。这就是 reordering产生的不好的后果,所以我们在某些时候为了避免这样的问题需要一些必要的策略,以保证多个线程一起工作的时候也存在一定的次序。 JMM提供了happens-before 的排序策略。这样我们可以得到多线程环境下的as-if-serial语义。
这里不对happens-before进行详细解释了,详细的请看这里http://www.ibm.com/developerworks/cn/java/j-jtp03304/,这里主要讲一下volatile在新的java内存模型下的变化,在jsr133之前,下面的代码可能会出现问题
1
2
3
4
5
6
7
8
9
10
11
12

Map configOptions;
char[] configText;
volatile boolean initialized = false;
// In Thread A
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;
// In Thread B
while (!initialized)
  sleep();
// use configOptions

jsr133之前,虽然对 volatile 变量的读和写不能与对其他 volatile 变量的读和写一起重新排序,但是它们仍然可以与对 nonvolatile 变量的读写一起重新排序,所以上面的Thread A的操作,就可能initialized变成true的时候,而configOptions还没有被初始化,所以initialized先于 configOptions被线程B看到,就产生问题了。

    JSR 133 Expert Group 决定让 volatile 读写不能与其他内存操作一起重新排序,新的内存模型下,如果当线程 A 写入 volatile 变量 V 而线程 B 读取 V 时,那么在写入 V 时,A 可见的所有变量值现在都可以保证对 B 是可见的。

结果就是作用更大的 volatile 语义,代价是访问 volatile 字段时会对性能产生更大的影响。这一点在ConcurrentHashMap中的统计某个segment元素个数的count变量中使用到了。


二.线程安全的HashMap



什么时候我们需要使用线程安全的hashmap呢,比如一个hashmap在运行的时候只有读操作,那么很明显不会有问题,但是当涉及到同时有改变也有读的时候,就要考虑线程安全问题了,在不考虑性能问题的时候,我们的解决方案有Hashtable或者 Collections.synchronizedMap(hashMap),这两种方式基本都是对整个hash表结构做锁定操作的,这样在锁表的期间,别的线程就需要等待了,无疑性能不高。


三.ConcurrentHashMap实现原理


数据结构





ConcurrentHashMap的目标是实现支持高并发、高吞吐量的线程安全的HashMap。当然不能直接对整个hashtable加锁,所以在ConcurrentHashMap中,数据的组织结构和HashMap有所区别。

一个ConcurrentHashMap由多个segment组成,每一个segment都包含了一个HashEntry数组的hashtable,
每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的 hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个 hashtable锁定的情况。

源码分析



在ConcurrentHashMap的remove,put操作还是比较简单的,都是将remove或者put操作交给key所对应的segment去做的,所以当几个操作不在同一个segment的时候就可以并发的进行。
	
    public V remove(Object key) {
    int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }

而segment中的remove操作除了加锁之外和HashMap中的remove操作基本无异。
        /**
         * Remove; match on key only if value null, else match both.
         */
        V remove(Object key, int hash, Object value) {
            lock();
            try {
                int c = count - 1;
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];
                HashEntry<K,V> e = first;
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue = null;
                if (e != null) {
                    V v = e.value;
                    if (value == null || value.equals(v)) {
                        oldValue = v;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        ++modCount;
                        HashEntry<K,V> newFirst = e.next;
                        for (HashEntry<K,V> p = first; p != e; p = p.next)
                            newFirst = new HashEntry<K,V>(p.key, p.hash,
                                                          newFirst, p.value);
                        tab[index] = newFirst;
                        count = c; // write-volatile
                    }
                }
                return oldValue;
            } finally {
                unlock();
            }
        }

上面的代码中关于volatile类型的变量count值得一提,这里充分利用了Java 5中对volatile语义的增强,count = c的操作必须在modCount,table等操作的后面,这样才能保证这些变量操作的可见性。
Segment类继承于ReentrantLock,主要是为了使用ReentrantLock的锁,ReentrantLock的实现比
synchronized在多个线程争用下的总体开销小。
put操作和remove操作类似。

接下来我们来看下get操作。
    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }

也是使用了对应的segment的get
	
       V get(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K,V> e = getFirst(hash);
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key)) {
                        V v = e.value;
                        if (v != null)
                            return v;
                        return readValueUnderLock(e); // recheck
                    }
                    e = e.next;
                }
            }
            return null;
        }

上面的代码中,一开始就对volatile变量count进行了读取比较,这个还是java5对volatile语义增强的作用,这样就可以获取变量的可见性。所以count != 0之后,我们可以认为对应的hashtable是最新的,当然由于读取的时候没有加锁,在get的过程中,可能会有更新。当发现根据key去找元素的时候,但发现找得的key对应的value为null,这个时候可能会有其他线程正在对这个元素进行写操作,所以需要在使用锁的情况下在读取一下 value,以确保最终的值。

其他相关涉及读取的操作也都类似。
分享到:
评论

相关推荐

    java8源码-FiveYears:学习/总结/成长/记录

    多线程 集合(底层源码) ArrayList LinkedList Vector HashMap ComcurrentHashMap LinkedHashMap Set IO 集合 源码学习 equals 编码规范 注解-未总结 :spider_web:前端 :revolving_hearts:数据结构和算法 CH LFU LRU ...

    基于Retinex模型与多尺度融合的低光照图像增强算法及其应用

    内容概要:本文介绍了一种基于Retinex模型和多尺度融合的低光照图像增强算法。首先,通过对原图像进行光照图分解并利用Retinex模型进行估计,再经过伽马矫正获得亮度均衡的图像。接着,为补偿伽马矫正当中的过曝细节丢失,进行了锐化处理以提升图像细节。最后,在多尺度融合金字塔模型下,根据不同输入图像的权重进行融合,从而得到最终的增强图像。文中还详细介绍了五个非参考图像质量评价指标(BRISQUE,CEIQ,ENIQA,NIQE,PIQE),用以评估算法的效果。 适合人群:从事计算机视觉、图像处理领域的研究人员和技术人员。 使用场景及目标:适用于需要在低光照条件下获取高质量图像的各种应用场景,如安防监控、自动驾驶、医疗影像等领域。目的是提高图像的亮度、对比度和细节,确保后续图像处理任务的有效性和准确性。 其他说明:该算法不仅提高了低光照环境拍摄照片的质量,也为其他计算机视觉应用提供了更好的图像素材,具有重要的社会和经济价值。

    scratch少儿编程逻辑思维游戏源码-奔跑吧!忍者.zip

    scratch少儿编程逻辑思维游戏源码-奔跑吧!忍者.zip

    基于人工蜂群算法的智能路径规划系统:全局搜索、鲁棒性强、灵活多用的路径规划解决方案

    内容概要:本文详细介绍了基于人工蜂群算法的路径规划系统。该算法模拟蜜蜂觅食行为,通过多个个体的并行搜索,实现了全局搜索能力强、鲁棒性和适应性强、适用范围广、算法设计灵活以及具有分布式计算能力等特点。文中还提供了简化的代码片段,展示了如何实现地图创建、保存和起始地点更改等功能,进一步解释了算法的具体实现方法。 适合人群:对路径规划算法感兴趣的科研人员、工程师和技术爱好者。 使用场景及目标:适用于复杂环境下的单目标或多目标路径规划问题,旨在帮助研究人员和开发者更好地理解和应用人工蜂群算法,提升路径规划系统的性能和效率。 其他说明:该算法不仅在理论上具有较高的研究价值,还在实际应用中展现了广泛的潜力,特别是在智能交通、机器人导航等领域。

    基于鲸鱼算法优化LSSVM回归模型:提高预测准确率与全局优化能力

    内容概要:本文介绍了如何使用鲸鱼算法优化最小二乘支持向量机(LSSVM)的回归预测模型。通过模拟鲸鱼群体的行为,优化LSSVM中的惩罚参数和核惩罚参数,提高了预测的准确性和可靠性。鲸鱼算法具有广泛的适用性、强大的全局优化能力和高效的计算特点,使其成为解决各类回归预测问题的有效工具。文中还提供了具体的Python代码实现,展示了从基本LSSVM预测到参数优化的具体步骤,并通过实验数据验证了优化后的模型在训练时间和预测精度上的显著优势。 适合人群:对机器学习、优化算法感兴趣的开发者和技术研究人员,尤其是希望深入了解和支持向量机优化的人群。 使用场景及目标:适用于需要提高回归预测准确性的应用场景,如金融预测、气象预报等领域。目标是通过优化模型参数,获得更高的预测精度和更快的计算速度。 其他说明:鲸鱼算法不仅在理论上具有优越性,在实际应用中也能显著提升模型性能。建议根据具体的数据规模调整算法参数,以达到最佳效果。

    scratch少儿编程逻辑思维游戏源码-超级猫.zip

    scratch少儿编程逻辑思维游戏源码-超级猫.zip

    scratch少儿编程逻辑思维游戏源码-超级马里奥世界 多人游戏.zip

    scratch少儿编程逻辑思维游戏源码-超级马里奥世界 多人游戏.zip

    scratch少儿编程逻辑思维游戏源码-丛林探险跑酷.zip

    scratch少儿编程逻辑思维游戏源码-丛林探险跑酷.zip

    【java】智能自助式停车场管理系统后台web管理服务器javaweb项目.zip

    【java】智能自助式停车场管理系统后台web管理服务器javaweb项目

    二阶系统PID控制器设计与仿真的灵活性及性能优化研究

    内容概要:本文详细介绍了二阶系统的PID控制器设计与仿真方法,展示了如何通过MATLAB进行系统建模和控制器参数调整。首先构建了一个典型的二阶系统作为例子,通过设置不同的PID参数(比例P、积分I、微分D),演示了如何优化系统的阶跃响应特性。文中还讨论了不同参数对系统稳定性的影响,以及如何应对非线性环节带来的挑战。此外,作者强调了PID控制器参数调整的重要性,并提供了几种实用技巧,如使用MATLAB内置工具pidTuner进行参数整定,以及尝试更换不同的被控对象来测试控制器的适应性和鲁棒性。 适合人群:自动化工程专业学生、从事工业控制系统设计的技术人员、对PID控制感兴趣的科研工作者。 使用场景及目标:① 学习如何利用MATLAB搭建二阶系统并设计PID控制器;② 掌握PID参数调整的基本方法及其对系统性能的影响;③ 提升解决实际工业控制问题的能力,特别是在面对复杂动态环境时。 阅读建议:读者可以通过跟随文中的步骤,在自己的环境中重现实验结果,从而加深对PID控制理论的理解。同时,鼓励读者尝试修改系统参数或引入新的干扰因素,进一步探索PID控制器的应用边界。

    少儿编程scratch项目源代码文件案例素材-扫雷.zip

    少儿编程scratch项目源代码文件案例素材-扫雷.zip

    少儿编程scratch项目源代码文件案例素材-圣诞老人VS机器人.zip

    少儿编程scratch项目源代码文件案例素材-圣诞老人VS机器人.zip

    基于AT89C51单片机交通灯课程设计

    【基于AT89C51单片机的交通灯系统】是电子工程领域中的一个经典实践项目,尤其适合初学者进行单片机编程和硬件控制的学习。AT89C51是一款广泛应用的8位微处理器,由美国Atmel公司生产,具有4KB的可编程Flash存储器,可以执行各种控制任务,包括交通灯系统的控制。 交通灯控制系统是城市交通管理的重要组成部分,通过红绿黄三色灯的变化来指示行人和车辆何时通行。在本项目中,交通灯系统采用AT89C51单片机作为核心控制器,通过编程实现红绿黄灯的定时切换,确保交通流畅且安全。 DSN(Design Suite Notation)文件,如`C51交通灯.DSN`,通常是在电路设计软件,如Keil uVision或Proteus中创建的工程文件。这种文件包含了整个项目的配置信息,包括源代码、元器件库、仿真设置等,使得开发者可以在虚拟环境中对交通灯系统进行仿真测试。Proteus是一款强大的电子电路仿真软件,可以直观地模拟硬件电路的行为,无需物理硬件即可验证设计的正确性。 数码管(7段显示器)是显示倒计时的关键部件。在这个项目中,数码管用于显示每个灯组的剩余时间,增强用户交互体验,使驾驶员和行人能够清晰了解何时转换灯色。AT89C51通过串行或并行接口与数码管连接,并通过特定的驱动程序代码控制数码管的显示内容。 编程方面,AT89C51使用C51语言编写,这是一种为8051系列单片机定制的C语言变体。代码中包含的详细注释对于初学者理解程序逻辑至关重要,通过注释可以学习如何设置定时器、中断服务子程序以及I/O端口操作,这些都是单片机编程的基础知识。 交通灯的控制通常基于定时器中断,例如,可以设置一个定时器在特定周期后触发中断,然后在中断服务程序中改变灯的状态。此外,为了实现数码管显示,可能需要用到移位寄存器和译码器等外围设备,这些都需要在代码中进行编程控制。 这个项目涵

    基于MATLAB的改进带记忆模拟退火算法求解TSP问题(多普勒型降温曲线)

    内容概要:本文介绍了一种基于MATLAB的改进带记忆模拟退火算法用于求解旅行商问题(TSP)。该算法引入了多普勒型降温曲线和记忆功能,使得算法在前期进行全局搜索而在后期进行精细调整。文中详细展示了算法的核心代码片段,如多普勒型降温曲线的实现和记忆功能的具体实现方式。此外,作者提供了对多个经典数据集(如att48、中国31/64/144城市数据)的测试结果,证明了该算法的有效性和优越性。同时,还给出了自定义数据集的测试方法和路径可视化的代码。 适合人群:对优化算法感兴趣的研究人员和技术爱好者,尤其是那些希望深入了解模拟退火算法及其应用的人群。 使用场景及目标:适用于需要解决复杂组合优化问题的场景,特别是涉及路径规划、物流配送等领域。目标是提供一种高效、稳定的解决方案,帮助用户快速获得高质量的解。 其他说明:本文不仅提供了完整的代码实现,还包括详细的解释和测试实例,便于读者理解和实践。对于想要进一步探索或修改算法的人来说,这是一个很好的起点。

    MMC-HVDC电能质量调节系统及其背靠背模块化多电平换流器在电网与粒子加速器中的应用

    内容概要:本文详细介绍了MMC-HVDC电能质量调节系统及其背靠背模块化多电平换流器(MMC)的工作原理和技术优势。MMC-HVDC系统主要用于保护敏感电网免受瞬态电压骤降的影响,通过内部能量存储和整流器控制线路电流,确保电网的稳定性。此外,该系统还具备无功功率补偿、低谐波失真和高冗余性的特点。文中特别提到MMC-HVDC在粒子加速器领域的应用和发展前景,强调了其在复杂环境中的适应性和可靠性。 适合人群:从事电力系统工程、电能质量管理、粒子加速器设计的研究人员和技术人员。 使用场景及目标:适用于需要解决瞬态电压骤降问题的电力系统,特别是在粒子加速器等对电能质量有较高要求的场合。目标是提高电网的稳定性和效率,减少设备损坏和系统不稳定性。 其他说明:文章还讨论了MMC-HVDC的设计和开发过程,包括模块化结构设计、能量存储优化和控制算法改进等方面的内容。

    少儿编程scratch项目源代码文件案例素材-侵略者.zip

    少儿编程scratch项目源代码文件案例素材-侵略者.zip

    scratch少儿编程逻辑思维游戏源码-暴徒危机.zip

    scratch少儿编程逻辑思维游戏源码-暴徒危机.zip

    少儿编程scratch项目源代码文件案例素材-收缩剑.zip

    少儿编程scratch项目源代码文件案例素材-收缩剑.zip

    少儿编程scratch项目源代码文件案例素材-忍者传奇.zip

    少儿编程scratch项目源代码文件案例素材-忍者传奇.zip

    scratch少儿编程逻辑思维游戏源码-抽象世界.zip

    scratch少儿编程逻辑思维游戏源码-抽象世界.zip

Global site tag (gtag.js) - Google Analytics