深入理解Java并发编程(一):线程安全

什么是线程安全

线程安全指某个函数、函数库在并发环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。

并发

提到线程安全,必须要提及的一个词就是并发,如果没有并发,那么也就不存在线程安全问题了。

并发,在操作系统中,是指在一个时间段内同时有几个程序处于启动运行和运行完毕之间,且这几个程序在同一个处理机上运行。

那么,操作系统是如何实现这种并发的呢?

操作系统是把CPU的时间划分成长短基本相同的时间区间,即“时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个作业使用。

如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做。此时CPU又分配给另一个作业去使用。

所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术并发完成的。

提到并发,还有另外一个词容易和他混淆,那就是并行。

并行,当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行。

多线程

对于操作系统来说,一个任务就是一个进程,比如打开一个浏览器就是启动一个浏览器进程,打开两个记事本就启动了两个记事本进程。

而在多个进程之间切换的时候,需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑,能不能在一个进程中增加一些“子任务”,这样减少上下文切换的成本。比如我们使用Word的时候,它可以同时进行打字、拼写检查、字数统计等,这些子任务之间共用同一个进程资源,但是他们之间的切换不需要进行上下文切换。

上下文切换就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。

在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

随着时间的慢慢发展,人们进一步的切分了进程和线程之间的职责。把进程当做资源分配的基本单元把线程当做执行的基本单元同一个进程的多个线程之间共享资源

拿Java语言来说,Java程序是运行在JVM上面的,每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中,又可以创建出很多线程,多个线程之间共享JVM资源,并且多个线程可以并发执行。

共享变量

共享变量,指的是多个线程都可以操作的变量。

前面提到过,进程是分配资源的基本单位,线程是执行的基本单位。所以,多个线程之间是可以共享一部分进程中的数据的。在JVM中,Java堆和方法区是多个线程共享的数据区域。也就是说,多个线程可以操作保存在堆或者方法区中的同一个数据。那么,换句话说,保存在堆和方法区中的变量就是Java中的共享变量。

Java中哪些变量是存放在堆中,哪些变量是存放在方法区中,又有哪些变量是存放在栈中的呢?

Java中共有三种变量,分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。

public class Variables {

    /**
     * 类变量
     */
    private static int a;

    /**
     * 成员变量
     */
    private int b;

    /**
     * 局部变量
     * @param c
     */
    public void test(int c){
        int d;
    }
}

上面定义的三个变量中,变量a就是类变量,变量b就是成员变量,而变量c和d是局部变量。

所以,变量a和b是共享变量,变量c和d是非共享变量。所以如果遇到多线程场景,对于变量a和b的操作是需要考虑线程安全的,而对于线程c和d的操作是不需要考虑线程安全的。

共享资源

同一进程中线程共享的资源到底有哪些?

线程的共性如下:

1、进程代码段;

2、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯);

3、进程打开的文件描述符;

4、信号的处理器;

5、进程的当前目录和进程用户ID与进程组ID。

除了上述线程的共性之外,线程当然还具有自己一些独立的资源。

线程的个性如下:

1、线程ID:每个线程都有自己的线程ID,这个ID在本进程中是唯一的,进程用此来标识线程。

2、寄存器组的值:由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有线程的寄存器集合的状态保存,以便将来该线程在被重新切换时能得以恢复。

3、线程的堆栈:堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。

4、错误返回码:由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用后设置了error值,而在该线程还没有处理这个错误,另外一个线程就在此时被调度器投入运行,这样错误值就有可能被修改。所以,不同的线程应该拥有自己的错误返回码变量。

5、线程的信号屏蔽码:由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。

6、线程的优先级:由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值