什么是线程安全和线程不安全

引入

当有多个线程共享同样的内存时:

  1. 如果
  • 线程不会对内存进行读取和修改
  • 内存是只读的
  • 多个进程对这个内存进行修改,但是写是原子操作时

那么线程是安全的

  1. 当一个线程修改变量时,其他线程在读取这个变量时可能看到一个不一致的值

在这里插入图片描述
我们需要使用锁来同步线程:同一时间只允许一个线程访问该变量
在这里插入图片描述

  1. 当多个线程试图在同一时间修改同一变量时,因为修改通常不是一步到位的:
  • 从内存中读入寄存器
  • 在寄存器中修改变量
  • 将新值写回内存

这个时候可能出现数据不一致,导致程序错误
在这里插入图片描述

  • 线程安全:指的是多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行线程时出现意外结果
  • 线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

有哪些问题可能导致线程不安全呢?

可能导致并发问题的原因:

  • 中断:假如有一段代码正在执行,如果发生了一个中断(中断是随机发生的),这份代码重新排队等待下一次调度。下一次调度重新运行当前代码段时(接着上次没有运行完的开始运行)共享内存可能被其他线程修改过了
  • 睡眠:假如有一段代码正在执行,如果源码中sleep了,这份代码重新排队等待下一次调度。下一次调度重新运行当前代码段时(接着上次没有运行完的开始运行)共享内存可能被其他线程修改过了
  • 同步:假如有一段代码正在执行,如果源码中想要获取获取一个互斥锁(等等),但是这个锁已经被占用了,这个时候代码就会重新排队等待下一次调度。下一次调度重新当前代码段时共享内存(接着上次没有运行完的开始运行)可能被其他线程修改过了
  • 对称多处理(SMP): SMP的多个CPU可以同时执行一段代码
  • 抢占式内核: 现在linux是抢占式内核,所以内核中的任务可能被另一个任务抢占,下一次调度重新运行当前代码段时(接着上次没有运行完的开始运行)共享内存可能被其他线程修改过

对称多处理技术:多个CPU之间没有区别,平等地访问内存、外设、一个操作系统。操作系统管理着一个队列,每个处理器依次处理队列中的进程

究竟什么是线程安全

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

我们把这个定义拆解一下,我们需要弄清楚这么几点: 1、并发 2、多线程/多进程 3、共享变量

并发

什么是并发

并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,而且这几个程序都是在同一个处理及上运行。

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

现在我们用到的操作系统,无论是windows还是linux都是多用户多任务分时操作系统。使用这些操作系统的用户是可以"同时"干多件事的。

但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。

但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。

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

由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。

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

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

ps: 并发与并行之间的关系
并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。

多线程

什么是多线程

理解了并发和并行之间的关系和区别后,我们再回到前面介绍的多任务分时操作系统,看看CPU是如何进行进程调度的。

为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户的各个任务使用。

在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。在操作系统中,CPU切换到另一个进程/线程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。上下文切换就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。

在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB还经常被称作“切换帧”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。

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

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

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

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

共享变量

什么是共享变量

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

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

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

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

/**
 * @author Hollis
 */
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的操作是不需要考虑线程安全的。

  • 线程安全问题都是由全局变量及静态变量引起的。
  • 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

在共享变量的前提下, 如何保证线程安全呢

确保同一时间只有一个线程能够访问共享资源

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值