精华内容
下载资源
问答
  • 来源:公众号【编程珠玑】作者:守望先生前言这些是编程语言中的基本概念,如果你还不是非常明确地清楚标题的问题,并且不知道...而常见作用域有以下几种:块作用域,可见范围是从定义处到包含该定义的块结尾函数作...

    来源:公众号【编程珠玑】

    作者:守望先生

    前言

    这些是编程语言中的基本概念,如果你还不是非常明确地清楚标题的问题,并且不知道作用域,链接属性,存储期等概念的具体含义,那么本文你不该错过。为了更加清晰的理解我们的问题,需要先了解三个概念:作用域,链接属性,存储期。

    作用域

    C语言中,作用域用来描述标识符能够被哪些区域访问。

    而常见作用域有以下几种:

    • 块作用域,可见范围是从定义处到包含该定义的块结尾

    • 函数作用域,goto语句的标签就具有函数作用域

    • 文件作用域,从定义处到定义该文件的末尾都可见。定义在函数之外的变量,就具有文件作用域了。

    • 函数原型作用域,从形参定义处到原型声明结束

    为了便于说明,我们来看一个例子,就很容易理解了:

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com
    ***************************************/

    #include 
    int num1 = 222;         //定位在函数外,具有文件作用域
    static int num2 = 111;  //定义在函数外,具有文件作用域
    int swap(int *a,int *b)//这里的a,b是函数原型作用域
    int swap(int *a,int *b){
        if(NULL== a || NULL == b)
            goto error;    
        else
        {
            int temp = *a;  //定义在函数内,块作用域
            *a = *b;
            *b = temp;
            return 0;
        }
        //printf("temp is %d\n",temp);   //因为temp具有块作用域,因此在这里不能直接使用
        error://goto语句的标签,函数作用域,因此在前面就可以引用
            {
                printf("input para is NULL\n");
                return -1;
            }
    }
    int main(void){
        printf("num1=%d,num2=%d\n",num1,num2);
        swap(&num1,&num2);  //num1 num2具有文件作用域,可以在main函数中直接使用
        printf("num1=%d,num2=%d",num1,num2);
        return 0;
    }

    可以看到,error标签具有函数作用域,整个函数内都可见,而temp具有块作用域,因此在大括号外部,不能直接使用它。而num1和num2具有文件作用域,因此main函数可以直接使用它。

    链接属性

    在《hello程序是如何变成可执行文件的》我们说到了编译的过程,最后一个步骤就是链接。链接属性决定了在不同作用域的同名标识符能否绑定到同一个对象或者函数。或者说,不同作用域的标识符在编译后是否是同一个实体。

    c变量有三种链接属性:

    • 外部链接,extern修饰的,或者没有static修饰的具有文件作用域的变量具有外部链接属性

    • 内部链接,static修饰的具有文件作用域的变量具有内部链接属性

    • 无链接,块作用域,函数作用域和函数原型作用域的变量无链接属性

    再稍作解释,没有static修饰,且具有文件作用域的变量,他们在链接时,多个同名标识符的变量最终都绑定到同一个实体。而static修饰的具有文件作用域的变量就不一样了,不同文件内,即便标识符名字相同,它们也绑定到了不同的实体。

    因此,如果我们希望某个变量或函数只在某一个文件使用,那么使用static修饰是一个很好的做法。

    同样的,来看一个例子。

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com***************************************/

    #include int a = 5;   //文件作用域,外部链接属性,其他文件可通过extern int a的方式使用该文件的astatic b = 6;  //文件作用域,内部链接属性,即便其他文件也有同名标识符,它们也是不同的int main(void)
    {int sum = 0 ; //无链接属性
        sum = a + b;
        printf("sum is %d\n",sum);return 0;
    }

    从代码中可以看到,a和b都具有文件作用域,a具有外部链接属性,而b具有内部链接属性,sum具有块作用域,因此无链接属性。

    存储期

    实际上作用域和链接属性都描述了标识符的可见性,而存储期则描述了这些标识符对应的对象的生存期。存储期,也分下面几种:

    • 静态存储期,程序执行期间一直都在,文件作用域的变量具有静态存储期

    • 自动存储期,它(变长数组除外)从块开始,到块末尾,因此,块作用域的变量具有自动存储期,它在栈中存储,需要显式初始化。

    • 动态分配存储期,即通过malloc分配内存的变量。它在堆中存储,需要显式初始。

    • 线程存储期,从名字可以知道, 它与线程相关,使用关键字_Thread_local声明的变量具有线程存储期,它从声明到线程结束一直存在。

    关于初始化,可参考《C语言入坑指南-被遗忘的初始化》。
    同样地,我们通过下面的代码来更好地理解存储期:

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com***************************************/

    #include int num1 = 222;         //静态存储期static int num2 = 111;  //静态存储期int add(int a,int b)
    {static int tempSum = 0;  //静态存储期
        tempSum = tempSum + a + b;return tempSum;
    }int main(void)
    {
        printf("num1=%d,num2=%d\n",num1,num2);int sum = 0;  //自动存储期
        sum = add(num1,num2);
        printf("first time sum=%d\n",sum);//sum = 333
        sum = add(num1,num2);
        printf("second time sum=%d\n",sum); //sum = 666return 0;
    }

    另外,如果我们通过nm命令查看编译出来的程序文件的符号表,我们可以找到num1,num2,tempSum,而没有sum,前者所用的内存数量在编译时就确定了。关于nm命令的使用可以参考《linux常用命令-开发调试篇》。

    $ gcc -g -o lifetime lifetime.c 
    $ nm lifetime|grep num1
    0000000000601038 D num1
    $ nm lifetime|
    grep num2
    000000000060103c d num2
    $ nm lifetime|grep tempSum
    0000000000601044 b tempSum.2289
    $ nm lifetime|
    grep sum
    $

    什么全局变量,局部变量,静态局部变量,静态全局变量

    到这里,我们就可以很容易区分上面的变量类型了。实际上这里只是换了一种说法:
    全局:具有文件作用域的变量
    静态:具有静态存储期或内部链接属性
    局部:具有函数或块作用域的变量

    因而结合起来,也就很好理解了。

    • 局部变量:函数或块作用域的变量

    • 静态局部变量:函数或块作用域,静态存储期

    • 全局变量:具有文件作用域的变量

    • 静态全局变量:内部链接属性的,具有文件作用域的变量

    当然,这仅仅是为了区分它们,这并不是它们的严格定义。更好的方法,是通过代码来理解:

    #include 
    int num1 = 222;         //全局变量
    static int num2 = 111;  //静态全局变量
    int add(int a,int b){
        static int tempSum = 0;  //静态局部变量
        tempSum = tempSum + a + b;
        return tempSum;
    }
    int main(void){
        printf("num1=%d,num2=%d\n",num1,num2);
        int sum = 0;  //局部变量
        sum = add(num1,num2);
        printf("first time sum=%d\n",sum);//sum = 333
        return 0;
    }

    总结

    本文总结如下:

    • 具有文件作用域的变量具有静态存储期,并且具有链接属性

    • 不希望其他文件访问的文件作用域变量最好使用static修饰

    • static关键字的含义需要结合上下文来理解

    • 如果可以,全局变量应该尽量避免使用,因为它可能带来变量被意外修改

    • 使用动态内存通常比栈内存慢,但是栈内存很有限

    参考

    https://en.wikipedia.org/wiki/Global_variables

    https://en.wikipedia.org/wiki/Local_variable

    《C11标准文档》

    关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源

    3d61e17b117a79b64f8a61f6c5c7d618.png

    展开全文
  • 前言这些是编程语言中的基本概念,如果你还不是非常明确地清楚标题的问题,并且不知道作用域,链接属性,存储期等概念的具体含义,那么...而常见作用域有以下几种:块作用域,可见范围是从定义处到包含该定义的块结...

    前言

    这些是编程语言中的基本概念,如果你还不是非常明确地清楚标题的问题,并且不知道作用域,链接属性,存储期等概念的具体含义,那么本文你不该错过。为了更加清晰的理解我们的问题,需要先了解三个概念:作用域,链接属性,存储期。当然需要注意的是,本文说明的是这些名称在C语言中的含义。

    作用域

    C语言中,作用域用来描述标识符能够被哪些区域访问。

    而常见作用域有以下几种:

    • 块作用域,可见范围是从定义处到包含该定义的块结尾
    • 函数作用域,goto语句的标签就具有函数作用域
    • 文件作用域,从定义处到定义该文件的末尾都可见。定义在函数之外的变量,就具有文件作用域了。
    • 函数原型作用域,从形参定义处到原型声明结束

    为了便于说明,我们来看一个例子,就很容易理解了:

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com
    ***************************************/
    #include <stdio.h>
    int num1 = 222;         //定位在函数外,具有文件作用域
    static int num2 = 111;  //定义在函数外,具有文件作用域
    int swap(int *a,int *b); //这里的a,b是函数原型作用域
    int swap(int *a,int *b)
    {
        if(NULL== a || NULL == b)
            goto error;    
        else
        {
            int temp = *a;  //定义在函数内,块作用域
            *a = *b;
            *b = temp;
            return 0;
        }
        //printf("temp is %dn",temp);   //因为temp具有块作用域,因此在这里不能直接使用
        error://goto语句的标签,函数作用域,因此在前面就可以引用
            {
                printf("input para is NULLn");
                return -1;
            }
    }
    int main(void)
    {
        printf("num1=%d,num2=%dn",num1,num2);
        swap(&num1,&num2);  //num1 num2具有文件作用域,可以在main函数中直接使用
        printf("num1=%d,num2=%d",num1,num2);
        return 0;
    }
    

    可以看到,error标签具有函数作用域,整个函数内都可见,而temp具有块作用域,因此在大括号外部,不能直接使用它。而num1和num2具有文件作用域,因此main函数可以直接使用它。

    链接属性

    在《hello程序是如何变成可执行文件的》我们说到了编译的过程,最后一个步骤就是链接。链接属性决定了在不同作用域的同名标识符能否绑定到同一个对象或者函数。或者说,不同作用域的标识符在编译后是否是同一个实体。

    c变量有三种链接属性:

    • 外部链接,extern修饰的,或者没有static修饰的具有文件作用域的变量具有外部链接属性
    • 内部链接,static修饰的具有文件作用域的变量具有内部链接属性
    • 无链接,块作用域,函数作用域和函数原型作用域的变量无链接属性

    再稍作解释,没有static修饰,且具有文件作用域的变量,他们在链接时,多个同名标识符的变量最终都绑定到同一个实体。而static修饰的具有文件作用域的变量就不一样了,不同文件内,即便标识符名字相同,它们也绑定到了不同的实体。

    因此,如果我们希望某个变量或函数只在某一个文件使用,那么使用static修饰是一个很好的做法。

    同样的,来看一个例子。

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com
    ***************************************/
    #include <stdio.h>
    int a = 5;   //文件作用域,外部链接属性,其他文件可通过extern int a的方式使用该文件的a
    static b = 6;  //文件作用域,内部链接属性,即便其他文件也有同名标识符,它们也是不同的
    int main(void)
    {
        int sum = 0 ; //无链接属性
        sum = a + b;
        printf("sum is %dn",sum);
        return 0;
    }
    

    从代码中可以看到,a和b都具有文件作用域,a具有外部链接属性,而b具有内部链接属性,sum具有块作用域,因此无链接属性。

    存储期

    实际上作用域和链接属性都描述了标识符的可见性,而存储期则描述了这些标识符对应的对象的生存期。存储期,也分下面几种:

    • 静态存储期,程序执行期间一直都在,文件作用域的变量具有静态存储期
    • 自动存储期,它(变长数组除外)从块开始,到块末尾,因此,块作用域的变量具有自动存储期,它在栈中存储,需要显式初始化。
    • 动态分配存储期,即通过malloc分配内存的变量。它在堆中存储,需要显式初始。
    • 线程存储期,从名字可以知道, 它与线程相关,使用关键字_Thread_local声明的变量具有线程存储期,它从声明到线程结束一直存在。

    关于初始化,可参考《C语言入坑指南-被遗忘的初始化》。
    同样地,我们通过下面的代码来更好地理解存储期:

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com
    ***************************************/
    #include <stdio.h>
    int num1 = 222;         //静态存储期
    static int num2 = 111;  //静态存储期
    int add(int a,int b)
    {
        static int tempSum = 0;  //静态存储期
        tempSum = tempSum + a + b;
        return tempSum;
    }
    int main(void)
    {
        printf("num1=%d,num2=%dn",num1,num2);
        int sum = 0;  //自动存储期
        sum = add(num1,num2);
        printf("first time sum=%dn",sum);//sum = 333
        sum = add(num1,num2);
        printf("second time sum=%dn",sum); //sum = 666
        return 0;
    }
    

    另外,如果我们通过nm命令查看编译出来的程序文件的符号表,我们可以找到num1,num2,tempSum,而没有sum,前者所用的内存数量在编译时就确定了。关于nm命令的使用可以参考《linux常用命令-开发调试篇》。

    $ gcc -g -o lifetime lifetime.c 
    $ nm lifetime|grep num1
    0000000000601038 D num1
    $ nm lifetime|grep num2
    000000000060103c d num2
    $ nm lifetime|grep tempSum
    0000000000601044 b tempSum.2289
    $ nm lifetime|grep sum
    $
    

    什么全局变量,局部变量,静态局部变量,静态全局变量

    到这里,我们就可以很容易区分上面的变量类型了。实际上这里只是换了一种说法:
    全局:具有文件作用域的变量
    静态:具有静态存储期或内部链接属性
    局部:具有函数或块作用域的变量

    因而结合起来,也就很好理解了。

    • 局部变量:函数或块作用域的变量
    • 静态局部变量:函数或块作用域,静态存储期
    • 全局变量:具有文件作用域的变量
    • 静态全局变量:内部链接属性的,具有文件作用域的变量

    当然,这仅仅是为了区分它们,这并不是它们的严格定义。更好的方法,是通过代码来理解:

    #include <stdio.h>
    int num1 = 222;         //全局变量
    static int num2 = 111;  //静态全局变量
    int add(int a,int b)
    {
        static int tempSum = 0;  //静态局部变量
        tempSum = tempSum + a + b;
        return tempSum;
    }
    int main(void)
    {
        printf("num1=%d,num2=%dn",num1,num2);
        int sum = 0;  //局部变量
        sum = add(num1,num2);
        printf("first time sum=%dn",sum);//sum = 333
        return 0;
    }
    

    总结

    本文总结如下:

    • 具有文件作用域的变量具有静态存储期,并且具有链接属性
    • 不希望其他文件访问的文件作用域变量最好使用static修饰
    • static关键字的含义需要结合上下文来理解
    • 如果可以,全局变量应该尽量避免使用,因为它可能带来变量被意外修改
    • 使用动态内存通常比栈内存慢,但是栈内存很有限

    本文最新内容地址:全局变量,局部变量,静态全局变量,静态局部变量

    欢迎批评指正。

    参考

    https://en.wikipedia.org/wiki/Global_variables

    https://en.wikipedia.org/wiki/Local_variable

    《C11标准文档》

    微信公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,数据结构与算法,工具,资源等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。

    6d9ee0286b93f16acec8216e093db19b.png

    变量,

    展开全文
  • 来源:公众号【编程珠玑】作者:守望先生前言这些是编程语言中的基本概念,如果你还不是非常明确地清楚标题的问题,并且不知道...而常见作用域有以下几种:块作用域,可见范围是从定义处到包含该定义的块结尾函数作...

    来源:公众号【编程珠玑】

    作者:守望先生

    前言

    这些是编程语言中的基本概念,如果你还不是非常明确地清楚标题的问题,并且不知道作用域,链接属性,存储期等概念的具体含义,那么本文你不该错过。为了更加清晰的理解我们的问题,需要先了解三个概念:作用域,链接属性,存储期。

    作用域

    C语言中,作用域用来描述标识符能够被哪些区域访问。

    而常见作用域有以下几种:

    • 块作用域,可见范围是从定义处到包含该定义的块结尾

    • 函数作用域,goto语句的标签就具有函数作用域

    • 文件作用域,从定义处到定义该文件的末尾都可见。定义在函数之外的变量,就具有文件作用域了。

    • 函数原型作用域,从形参定义处到原型声明结束

    为了便于说明,我们来看一个例子,就很容易理解了:

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com
    ***************************************/

    #include 
    int num1 = 222;         //定位在函数外,具有文件作用域
    static int num2 = 111;  //定义在函数外,具有文件作用域
    int swap(int *a,int *b)//这里的a,b是函数原型作用域
    int swap(int *a,int *b){
        if(NULL== a || NULL == b)
            goto error;    
        else
        {
            int temp = *a;  //定义在函数内,块作用域
            *a = *b;
            *b = temp;
            return 0;
        }
        //printf("temp is %d\n",temp);   //因为temp具有块作用域,因此在这里不能直接使用
        error://goto语句的标签,函数作用域,因此在前面就可以引用
            {
                printf("input para is NULL\n");
                return -1;
            }
    }
    int main(void){
        printf("num1=%d,num2=%d\n",num1,num2);
        swap(&num1,&num2);  //num1 num2具有文件作用域,可以在main函数中直接使用
        printf("num1=%d,num2=%d",num1,num2);
        return 0;
    }

    可以看到,error标签具有函数作用域,整个函数内都可见,而temp具有块作用域,因此在大括号外部,不能直接使用它。而num1和num2具有文件作用域,因此main函数可以直接使用它。

    链接属性

    在《hello程序是如何变成可执行文件的》我们说到了编译的过程,最后一个步骤就是链接。链接属性决定了在不同作用域的同名标识符能否绑定到同一个对象或者函数。或者说,不同作用域的标识符在编译后是否是同一个实体。

    c变量有三种链接属性:

    • 外部链接,extern修饰的,或者没有static修饰的具有文件作用域的变量具有外部链接属性

    • 内部链接,static修饰的具有文件作用域的变量具有内部链接属性

    • 无链接,块作用域,函数作用域和函数原型作用域的变量无链接属性

    再稍作解释,没有static修饰,且具有文件作用域的变量,他们在链接时,多个同名标识符的变量最终都绑定到同一个实体。而static修饰的具有文件作用域的变量就不一样了,不同文件内,即便标识符名字相同,它们也绑定到了不同的实体。

    因此,如果我们希望某个变量或函数只在某一个文件使用,那么使用static修饰是一个很好的做法。

    同样的,来看一个例子。

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com***************************************/

    #include int a = 5;   //文件作用域,外部链接属性,其他文件可通过extern int a的方式使用该文件的astatic b = 6;  //文件作用域,内部链接属性,即便其他文件也有同名标识符,它们也是不同的int main(void)
    {int sum = 0 ; //无链接属性
        sum = a + b;
        printf("sum is %d\n",sum);return 0;
    }

    从代码中可以看到,a和b都具有文件作用域,a具有外部链接属性,而b具有内部链接属性,sum具有块作用域,因此无链接属性。

    存储期

    实际上作用域和链接属性都描述了标识符的可见性,而存储期则描述了这些标识符对应的对象的生存期。存储期,也分下面几种:

    • 静态存储期,程序执行期间一直都在,文件作用域的变量具有静态存储期

    • 自动存储期,它(变长数组除外)从块开始,到块末尾,因此,块作用域的变量具有自动存储期,它在栈中存储,需要显式初始化。

    • 动态分配存储期,即通过malloc分配内存的变量。它在堆中存储,需要显式初始。

    • 线程存储期,从名字可以知道, 它与线程相关,使用关键字_Thread_local声明的变量具有线程存储期,它从声明到线程结束一直存在。

    关于初始化,可参考《C语言入坑指南-被遗忘的初始化》。
    同样地,我们通过下面的代码来更好地理解存储期:

    /****************************
    作者:守望先生
    来源:公众号编程珠玑
    个人博客:https://www.yanbinghu.com***************************************/

    #include int num1 = 222;         //静态存储期static int num2 = 111;  //静态存储期int add(int a,int b)
    {static int tempSum = 0;  //静态存储期
        tempSum = tempSum + a + b;return tempSum;
    }int main(void)
    {
        printf("num1=%d,num2=%d\n",num1,num2);int sum = 0;  //自动存储期
        sum = add(num1,num2);
        printf("first time sum=%d\n",sum);//sum = 333
        sum = add(num1,num2);
        printf("second time sum=%d\n",sum); //sum = 666return 0;
    }

    另外,如果我们通过nm命令查看编译出来的程序文件的符号表,我们可以找到num1,num2,tempSum,而没有sum,前者所用的内存数量在编译时就确定了。关于nm命令的使用可以参考《linux常用命令-开发调试篇》。

    $ gcc -g -o lifetime lifetime.c 
    $ nm lifetime|grep num1
    0000000000601038 D num1
    $ nm lifetime|
    grep num2
    000000000060103c d num2
    $ nm lifetime|grep tempSum
    0000000000601044 b tempSum.2289
    $ nm lifetime|
    grep sum
    $

    什么全局变量,局部变量,静态局部变量,静态全局变量

    到这里,我们就可以很容易区分上面的变量类型了。实际上这里只是换了一种说法:
    全局:具有文件作用域的变量
    静态:具有静态存储期或内部链接属性
    局部:具有函数或块作用域的变量

    因而结合起来,也就很好理解了。

    • 局部变量:函数或块作用域的变量

    • 静态局部变量:函数或块作用域,静态存储期

    • 全局变量:具有文件作用域的变量

    • 静态全局变量:内部链接属性的,具有文件作用域的变量

    当然,这仅仅是为了区分它们,这并不是它们的严格定义。更好的方法,是通过代码来理解:

    #include 
    int num1 = 222;         //全局变量
    static int num2 = 111;  //静态全局变量
    int add(int a,int b){
        static int tempSum = 0;  //静态局部变量
        tempSum = tempSum + a + b;
        return tempSum;
    }
    int main(void){
        printf("num1=%d,num2=%d\n",num1,num2);
        int sum = 0;  //局部变量
        sum = add(num1,num2);
        printf("first time sum=%d\n",sum);//sum = 333
        return 0;
    }

    总结

    本文总结如下:

    • 具有文件作用域的变量具有静态存储期,并且具有链接属性

    • 不希望其他文件访问的文件作用域变量最好使用static修饰

    • static关键字的含义需要结合上下文来理解

    • 如果可以,全局变量应该尽量避免使用,因为它可能带来变量被意外修改

    • 使用动态内存通常比栈内存慢,但是栈内存很有限

    参考

    https://en.wikipedia.org/wiki/Global_variables

    https://en.wikipedia.org/wiki/Local_variable

    《C11标准文档》

    关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源

    3cf6f122143c7f1d7b323bd8dc8906e1.png

    展开全文
  • 本文就来探讨这些问题的答案,从根本上了解变量的读写性能都和哪些因素有关。 著作权声明 本文译自 Nicholas C. Zakas 于2009年2月10日在个人网站上发表的《JavaScript Variable Performance》。原文是唯一的正式版...
  • 翻译:GentlemanTsao,2020-4-29 可以由多个线程同时安全调用的代码称为线程安全代码。线程安全的代码不包含竞态条件。只有当多个线程更新共享资源时,才会出现竞态条件。...以下是线程安全的原始局部变量的示例:

    翻译:GentlemanTsao,2020-4-29


    可以由多个线程同时安全调用的代码称为线程安全代码。线程安全的代码不包含竞态条件。只有当多个线程更新共享资源时,才会出现竞态条件。因此,了解Java线程在执行时共享了哪些资源非常重要。

    局部变量

    局部变量存储在线程自己的堆栈中。这意味着局部变量永远不会在线程之间共享。这也意味着所有的原始局部变量都是线程安全的。以下是线程安全的原始局部变量的示例:

    public void someMethod(){
    
      long threadSafeInt = 0;
    
      threadSafeInt++;
    }

    局部对象引用

    对象的局部引用有点不同,引用本身是不共享的。但是,引用的对象并不是存储在每个线程的局部栈中,而是所有对象都存储在共享堆中。

    如果局部创建的对象从不在创建它的方法之外使用,则它是线程安全的。实际上,你也可以把它传递给其他方法和对象,只要这些方法或对象不会再把它给其他线程使用。

    以下是线程安全局部对象的示例:

    public void someMethod(){
    
      LocalObject localObject = new LocalObject();
    
      localObject.callMethod();
      method2(localObject);
    }
    
    public void method2(LocalObject localObject){
      localObject.setValue("value");
    }
    

    本例中的LocalObject实例不会从该方法返回,也不会传递给someMethod()方法外部可访问的其他对象。每个执行someMethod()方法的线程都将创建自己的LocalObject实例并将其分配给localObject引用。因此,这里使用的LocalObject是线程安全的。

    实际上,整个方法someMethod()都是线程安全的。即使LocalObject实例作为参数传递给同一个类的其他方法,或其他类中的方法,它的使用也是线程安全的。

    当然,唯一的例外是,如果其中某个方法使用LocalObject作为参数调用,又存储了LocalObject实例,而存储的实例允许其他线程访问。

    对象成员变量

    对象成员变量(字段)与对象一起存储在堆中。因此,如果两个线程调用同一个对象实例的方法,并且此方法更新对象成员变量,则该方法不是线程安全的。下面是一个非线程安全的方法示例:

    public class NotThreadSafe{
        StringBuilder builder = new StringBuilder();
    
        public add(String text){
            this.builder.append(text);
        }
    }

    如果两个线程同时调用同一个NotThreadSafe实例的add()方法,则会导致竞态条件。例如:

    NotThreadSafe sharedInstance = new NotThreadSafe();
    
    new Thread(new MyRunnable(sharedInstance)).start();
    new Thread(new MyRunnable(sharedInstance)).start();
    
    public class MyRunnable implements Runnable{
      NotThreadSafe instance = null;
    
      public MyRunnable(NotThreadSafe instance){
        this.instance = instance;
      }
    
      public void run(){
        this.instance.add("some text");
      }
    }

    注意下两个MyRunnable实例是如何共享同一个NotThreadSafe实例的。因此,当它们调用NotThreadSafe实例的add()方法时,会导致竞态条件。

    但是,如果两个线程同时调用不同实例的add()方法,则不会导致竞态条件。下面是在之前的示例上稍作修改:

    new Thread(new MyRunnable(new NotThreadSafe())).start();
    new Thread(new MyRunnable(new NotThreadSafe())).start();

    现在这两个线程都有自己的NotThreadSafe实例,因此它们对add方法的调用不会相互干扰。代码不存在竞态条件了。所以,即使一个对象不是线程安全的,它仍然有避免竞态条件的使用方式。

    线程控制逸出规则

    当试图确定代码对某个资源的访问是否是线程安全时,可以使用线程控制逸出规则:

    如果资源的创建、使用和释放(译者注:原文为disposed,这里的意思是丢弃)是在同一个线程的控制下,
    并且永远不会逃离出该线程的控制,
    则该资源的使用是线程安全的。

    资源可以是任何共享资源,如对象、数组、文件、数据库连接、套接字等。在Java中,并不总是显式地释放对象,因此“释放”意味着丢弃或将对象的引用置空。

    即使对象的使用是线程安全的,但如果该对象指向共享资源(如文件或数据库),则整个应用程序可能不是线程安全的。例如,如果线程1和线程2各自创建自己的数据库连接:连接1和连接2,则每个连接本身的使用是线程安全的。但是连接指向的数据库的使用可能不是线程安全的。例如,如果两个线程都执行这样的代码:

    检查是否存在记录X
    如果不存在,插入记录X

    如果两个线程同时执行此操作,并且它们正在检查的记录X恰好是同一个记录,则有可能两个线程最终都会插入该记录。就像这样:

    线程 1 检查是否存在记录X. Result = no
    线程 2 检查是否存在记录X. Result = no
    线程 1 插入记录X
    线程 2 插入记录X

    线程在操作文件或其他共享资源时也可能发生这种情况。因此,有必要认清线程控制的对象究竟是资源,或者仅仅是资源的引用(就像数据库连接那样)。

    下一篇:
    java并发和多线程教程(十):线程安全和不变性

    更多阅读:
    专栏:java并发和多线程教程

    展开全文
  • 本文就来探讨这些问题的答案,从根本上了解变量的读写性能都和哪些因素有关。 著作权声明 本文译自Nicholas C. Zakas于2009年2月10日在个人网站上发表的《JavaScript Variable Performance》。原文是唯一的正式版,...
  • jvm栈帧包含哪些内容

    千次阅读 2018-06-07 15:59:46
    栈帧:前面说了,当线程执行到某个方法时就会往方线程栈中压入一个帧,称为栈帧,栈帧中包含了方法的局部变量表、操作数栈、返回地址、动态连接等信息局部变量表:顾名思义就是用来存储java方法中的局部变量的,在...
  • 每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。 之前我们一直讲的栈区域实际上...
  • Python局部函数及用法(包含nonlocal关键字)通过前面的学习我们知道,Python 函数内部可以定义变量,这样就产生了局部变量,有读者可能会问,Python 函数内部能定义函数吗?答案是肯定的。Python 支持在函数内部定义...
  • 堆栈溢出(使用了过大的局部变量,因为局部变量存储在栈中,容易导致溢出;函数递归的深度太大等等) 非法指针(比如随意的强制转换指针的类型,或者使用了空指针) 多线程同时读写某一个变量时,没有加线程锁。
  • static关键字不能应用于局部变量,它只能作用于域。 静态初始化只在必要的时刻才会进行——第一次访问静态数据,或者第一次创建对象。 三、概述 (包含继承) public static void main(String[]
  • 垃圾回收算法有哪些

    2020-08-26 11:49:26
    虚拟机的根对象集合根据实现不同而不同,包含局部变量中的对象引用和栈帧的操作数栈(以及类变量中的对象引用)、被加载的类的常量池中的对象引用(比如字符串)、传递到本地方法中的没有被本地方法释放的对象引用。...
  • 2.局部变量命名、静态成员变量命名:只能包含字母,单词首字母出第一个都为大写,其他字母都为小写; 3.常量命名:只能包含字母和_,字母全部大写,单词之间用_隔开; 4.layout中的id命名:命名模式为:view缩写_...
  • 2.局部变量命名、静态成员变量命名:只能包含字母,单词首字母出第一个都为大写,其他字母都为小写; 3.常量命名:只能包含字母和_,字母全部大写,单词之间用_隔开; 4.layout中的id命名:命名模式为:view缩写_...
  • 垃圾检测通常通过建立一个根对象的集合以及建立一个从这些根对象开始能够触及的对象集合来实现。...虚拟机的根对象集合根据实现不同而不同,包含局部变量中的对象引用和栈帧的操作数栈(以及类变量中的对...
  • 在这篇文章之中我们来了解一下python这门编程语言之中命名空间和作用域。在这一篇文章之中我们将会来了解一下python命名空间,以及命名空间和作用域。了解关于他们的一些知识。...如果一个局部变量和一个全局变...
  • 2. 栈:保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区对象的引用(指针)。也可以用来保存加载方法时的帧。 3. 堆:用来存放动态产生的数据,比如new出来的对象。注意创建出来的对象只...
  • 按计划,JDK 10 将于 3 月 20 日...新特性中最重要的,是我们之前曾报道过的 12 个 JEP :JEP 286: 局部变量的类型推断。该特性在社区讨论了很久并做了调查,可查看 JEP 286 调查结果。JEP 296: 将 JDK 的多个代码
  • 栈区:存放局部变量,对象声明的引用等。堆区:存放 new关键字 创建的类(包含成员变量)和数组等。堆与栈的优缺点栈的优点:栈数据可以 共享 ,存取速度比堆 快 。 缺点是:存在栈中 数据大小与生命周期是确定 的。堆...
  • 代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系。 关联:对象之间一种引用关系,比如客户类与订单类之间的关系。这种关系通常使用类的属性表达。 聚合:表示has-a的关系,是一种不...
  • 局部变量包含该方法内相关的所有局部变量,包括方法内的局部变量,以及传入方法的参数。数据类型是几种基本的数据类型,以及引用类型。 4.1.2 操作数栈 操作数栈用于操作的执行,方法进行操作时,将.
  • 共享资源局部变量局部变量存储在线程自己的栈中.也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是安全的. public int fun(){ int a=10; return a; } 这个方法就是线程安全的 局部的
  • 注解与类、接口、枚举是在同一个层次,可以用来标注包、类、字段、方法、局部变量、方法参数等元素,达到对这些元素的描述和说明。注解是可以允许jvm在运行中读取它,这一点与注释完全不同。并且包含多种加载策略,...
  • JVM之Runtime Data Area和JVM Instructions分析结构图包含哪些组件以及作用局部变量表 结构图 包含哪些组件以及作用 一个java文件在javac指令后编译成class文件会经过 load、link、initializing后进入到runtime ...
  • 文章目录前言一、Local variables (本地变量/局部变量)二、Local Object References (本地对象引用/局部对象引用)三、Object Member Variables (对象成员变量)四、线程安全规则 前言 线程安全:代码同时被多个线程...
  • JVM

    2018-05-14 09:51:49
    线程私有的 程序计数器+虚拟机栈一个方法一个栈帧,JVM很早就知道哪些方法调用哪些方法,所以包含局部变量表操作数栈动态链接----支撑了允许时多态,接口.funtion, 底层实际是来解析该接口的实现类,出口虚拟机栈...
  • 原文链接:http://tutorials.jenkov.com/java-concurrency/thread-safety.html可以被多个线程同时调用的安全代码叫做线程安全。...局部变量局部变量被保存在每个线程自己的栈中,就是说局部变量永远也

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 140
精华内容 56
关键字:

局部变量包含哪些