• 局部变量保证线程安全


    局部变量保证线程安全

    首先来看String这个类的hashcode方法,如下

    public int hashCode()
    {
        int h = hash; /* 代码① */
        if ( h == 0 && value.length > 0 )
        {
            char val[] = value;
     
            for ( int i = 0; i < value.length; i++ )
            {
                h = 31 * h + val[i];
            }
            hash = h;       /* 代码② */
        }
        return(h);              /* 代码③ */
    }
    

    hashString类的一个属性,可以看到这边首先是代码①读取了本地属性的值,并且赋值给局部变量h。并且使用h进行了业务逻辑的判断。如果h没有值的话,就进行 Hash 值的生成,并且赋值到h上,并且在代码②处赋值给了属性hash。最终返回的,也是局部变量h的值。那么上述的代码能否修改为下面的模式

    public int hashCode()
    {
        if ( hash == 0 && value.length > 0 )  /* 代码① */
        {
            char    val[]    = value;
            int    h    = 0;
            for ( int i = 0; i < value.length; i++ )
            {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return(hash); /* 代码② */
    }
    

    修改的代码没有局部变量,直接使用属性本身来操作。

    答案是否定的,因为这种写法是线程不安全的,可能导致方法的返回值是 0 。似乎有点费解,因为如果hash值为0 ,则代码会进入循环体,对hash值进行更新。所以乍看之下,无论如何是不会返回 0 的。

    上述的理解逻辑,在单线程环境下,是正确的。但是这段代码工作在多线程环境。实际上,上述代码有两次对hash值的读取,分别是代码①和②。可能会出现一种情况,在代码①处,读取到hash值不为 0 ,在代码②处,读取到hash值为0,并且以此为结果返回了。显然此时这种结果是错误的。

    要理解这种场景的发生需要从 JMM 的规则谈起。首先,两个读取之间是没有因果关系的,因此不存在第一个对变量的读取观察到了值,第二个对该变量的读取也要观察到这个值。其次,在 JMM 中,对一个变量的读取操作允许其观察最后一次到对该变量的写入,只要没有 HB 关系来阻止这个读取的观察效果。此外,对象属性的默认值也是由写入动作触发的。这意味着对hash值的写入有两个地方,一个在于对象构造时,一个在于其他线程对hash值的写入。由于这两个写入没有 HB 关系,因此对hash的读取可能读取到任意一个写入的结果。所以,可能会出现的情况是在代码①处读取到了其他线程对hash值的写入,因此跳过了内部的写入逻辑。而在代码②处再次读取hash值,此时读取到了对象构造时对hash默认值的写入,导致返回 0 。

    从 JMM 规则角度是最正确的理解,但是为了形象的想象这一切如何发生,我们可以将上面的程序修改如下

    public int hashCode()
    {
        int a = hash;
        if ( hash == 0 && value.length > 0 ) /* 代码① */
        {
            char    val[]    = value;
            int    h    = 0;
            for ( int i = 0; i < value.length; i++ )
            {
                h = 31 * h + val[i];
            }
            a = hash = h;
        }
        return(a); /* 代码② */
    }
    

    实际上,这的确是在执行代码逻辑的时候,一种可能的代码重排序变种。假定一开始hash值为0,则a为 0 。在if判断的时候,hash读取到了其他线程写入的值,因此没有执行计算逻辑,最终返回了a的值,也就是 0 。

  • 相关阅读:
    JSON格式
    多行写入
    文件对象write() and read()
    一个虚拟摄像头Filter(Virtual Cam Capture Filter)
    五十种最好的开源爬虫
    web scraper 里的 Element click 模拟点击「加载更多」
    介绍一款好用又易学的爬虫工具:web scraper
    安装宝塔面板后 ,centos系统 挂载硬盘 或者 数据盘和系统盘合并
    帝国CMS恢复搜索功能 增加搜索数据源设置教程
    安装帝国CMS步骤 和恢复数据
  • 原文地址:https://www.cnblogs.com/jfire/p/12715397.html
Copyright © 2020-2023  润新知