3、并发编程需要知道的一些基本概念

一、基本概念

1、串行、并行和并发

串行:按顺序逐一完成几件事情,只需要一个线程投入
并发:在一段时间内以交替的方式去完成多个任务
并行:可看作是并发的特例,在同一时间去完成多个任务

并行和并发区别

(1)并发侧重于多个任务交替执行,多个任务之间可能是串行的,而并发侧重多个任务真正意义上“同时执行”(2)从硬件角度考虑,一个处理器一次只能运行一个线程,而处理器以时间分片技术来实现同一个时间段可以运行多个线程,因此一个处理器就能实现并发。但是对于并行,必须要有多个处理器来实现。

  • 多线程编程的实质就是将任务的处理方式由串行改为并发,并实现并行化,以发挥并发的优势
2、同步和异步

同步:同步方法调用一旦开始,调用者必须等到调用方法调用完成返回后,才能继续后续的行为。
异步:异步方法更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。异步方法通常会在另一个线程中真实的执行。

同步方法和异步方法比较

3、临界区

临界区:临界区用来表示一种公共资源或者说是共享资源,可以被多个线程使用。但是一次只能被一个线程使用者,一旦临界区资源被占用,其他线程要想使用这个线程,就必须等待。

  • 在多线程环境中,临界区资源是受保护的对象。
4、阻塞与非阻塞

阻塞:线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待,等待会导致线程的挂起,这种情况就是阻塞。
非阻塞:强调没有一个线程可以妨碍其他线程执行,所有线程都会尝试不断向前执行。

5、死锁、饥饿和活锁
  • 死锁、饥饿和活锁都属于多线程活跃问题,后续会详解,这里只是简单介绍其概念。
    死锁:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
    饥饿:指一个或者多个线程因为某种原因无法获取锁需要的资源,导致一直无法执行,比如线程优先级太低,而高优先级的线程不断抢夺它的资源。
    活锁:指资源不断的在几个线程中跳跃,但是没有一个线程可以拿到资源而正常执行。
6、竞态

状态变量:类的实例变量或者静态变量
共享变量:可以被多个线程共同访问的变量,状态变量由于可以被多个线程共享,因此被称为共享变量。
竞态:由于不恰当的执行时序而出现不正确的结果是一种现象称为静态条件,导致竞态条件发生的代码区称作临界区。

导致竞态发生的常见原因是多个线程没有采取任何控制措施的条件下面并发的更新、读取一个共享变量。

静态往往伴随着读取到脏数据问题,即读取到一个过时的数据、丢失更新问题(及一个线程对数据所做的更新没有体现在后续其他线程对该数据的读取上)。

(1)竞态模式

  • 竞态的两种模式:read-modify-write(读-写-改)和check-then-act(检查而后行动)
    read-modify-write:该模式细分为几个操作,读取一个变量的的值,然后根据值做一些计算,接着更新该共享变量非值。比如共享变量sequence++这个操作,大致可以拆解为三个操作:(1)将变量sequence的值从内存读取到寄存器(2)将寄存器值加1(3)将寄存器中变更的值写入到变量sequence所对应的内存空间。由于没有做任何的控制处理,上诉三个操作的原子性、可见性、有序性不能得到保证,就有可能出现并发问题

一个线程执行完指令(1)之后到开始指令(2)的这段时间内,其他线程已经更新了共享变量的值,这就使得该线程在执行指令(2)时候使用的是共享变量的旧值(读脏数据),接着该线程把根据旧值计算出的结果更新到共享变量,使的其他线程所做的更新被覆盖,造成了丢失更新

check-then-act:这个模式步骤可以细分为,读取某一个共享变量的值,根据该变量的值决定下一步的动作是什么。例如:

if(sequence>999){//(1)操作一check:检查共享变量的值
sequence=0;}//(2)操作二act:下一步的操作

  • 一个线程执行完操作(1)到开始执行操作(2)这段时间内,其他线程可能已经更新了共享变量从而使if条件变为不成立,那么此时该线程任然会执行操作(2),这样也会造成丢失更新问题。

(2)竞态条件

  • 设a和b是并发访问共享变量v的两个操作,这两个操作并发都是读操作。如果一个线程正在执行a期间(开始执行但是未执行结束)另一个线程正在执行b,那么无论b是在读取还是在更新v都可能会导致竞态。因此竞态可以看做访问(读、写)同一组共享变量的多个线程所执行的操作相互交错。
  • 对于局部变量(包括形参和方法内部的变量),由于不同线程各自访问的各自的那一份局部变量,因此局部变量的使用不会导致竞态发生。

二、多线程编程概念

1、什么是多线程编程
  • 多线程编程是以线程单位的一种编程范式,但多线程编程并非仅仅是使用多个线程编程那么简单。
2、多线程编程优势和劣势

优势:

1、提高系统的吞吐量
2、提高响应性
3、充分利用多个处理器资源
4、最小化对资源的利用
5、简化程序结构

多线程需要解决的问题:

1、线程安全问题
2、线程活性问题(死锁、饥饿、活锁)
3、上下文切换问题
4、可靠性问题

三、所线程编程面临的挑战

1、上下文切换
(1)基本内容

分时操作系统调度:在分时操作系统中,资源调度是基于时间片的。时间片决定了一个线程可以连续占用处理器运行的时间长度。
线程上下文切换:当一个线程由于时间片用完或者自身原因被迫或者主动暂停其运行时,另一个线程(可能是同一个进程的线程或者其他进程的线程)可以被操作系统调度选中占用处理器开始或者继续运行。这种线程被暂停(及被剥夺处理器的使用权),另一个线程被选中,获取时间片,开始或者继续运行的过程被称为线程上下文切换,简称上下文切换。
切出:一个线程被剥夺处理器的使用权而暂停运行被称为切出
切入:一个线程被操作系统选中占用处理器开始或者继续其运行称为切入
上下文:切入、切出时候需要保存和恢复相应线程的进度信息,这个进度信息就成为上下文。一般包括寄存器内容和程序计数器内存。


具体这部分内柔可以参考操作系统文章:https://www.jianshu.com/p/d79e4dc9a270

(2)、java程序中的上下文切换
  • 从应用程序的角度看,一个线程的生命周期状态runnable状态与非runnable状态(blocking、waiting等)之间的切换过程就是一个上下文切换。当runnable进入非runnable状态的时候称为线程暂停,当非runnable状态变为runnable状态的时候称为唤醒
(3)、上下文分类

自发性上下文切换:指线程由于自身因素导致的切出,从java平台角度看,一个线程再其运行环境中执行下列任何一个方法都会发生自发性上下文切换(sleep/wait/yield/join等)
非自发性上下文切换:由于线程调度器的原因被迫切出,比如时间片被用完、一个优先级更好的线程需要执行,在比如jvm执行垃圾回收时候的stop-world也是会造成非自愿性的上下文切换。

(4)上下文切换带来的开销

直接开销
1、操作系统保存和恢复上下文资源需要的开销,主要是处理器时间开销
2、线程调度器进行线程调度开销
间接开销
3、处理器高速缓存重新加载的时间开销
4、上下文切换也可能导致整个一级高速缓存中的内存被冲刷(冲刷到二级缓存、三级缓存或者主内存RAM)

  • 从定量的角度看,一次上下文切换的时间 消耗大概是微秒(us)级别的,但是多线程编程中,多线程的数量越多,消耗的资源越多
  • 多线程编程相对于单线程编程来说,意味着一个上下文切换的开销,多线程编程不一定比单线程编程的计算效率更高。
(5)linux中查看上下文切换测量

参考:

2、线程的活性故障

线程活性故障:由于线程资源稀缺性或者程序自身的问题和缺陷导致线程线程一直处于非runnable状态,或者线程虽然处于runnable状态但是其要执行的任务却一直无法进展的现象被称为线程活性故障。
常见的线程活性故障包括死锁、活锁、饥饿。

3、资源的争用和调度

排他性资源:一次只能被一个线程所占有的资源,常见的排他性资源包括处理器、数据库连接、文件等。
资源争用:在一个线程占用一个排他性资源进行访问而未释放其对资源所有权的时候,其他线程试图访问这个资源的现象。
高并发:同一个时间内,处于运行态(runnable)的线程数量越多,就称为并发程度越高,就称为高并发。

虽然高并发增加资源争用的概率,但是高并发未必意味着资源争用。理想情况就是高并发、低争用

资源的调度:在多个线程申请同一个排他性资源情况下面,决定哪一个线程会被授予该资源的独占性,即选择哪一个申请者占用资源的过程称为资源的调度。
公平性:资源的申请者(线程)是否按照其申请(请求)资源的顺序而授予资源的独占权。
公平性调度和非公平性调度:如果一个资源的任何一个先申请者总是能够比任何一个后申请者先或的该资源的独占权,那么相应的资源调度策略就是公平性调度;否则就是非公平性调度

  • 资源调度一种常见策略就是排队,公平性调度不允许插队现象,而非公平性调度允许插队现象,但是非公平性调度中在极端情况下面会产生饥饿显现(就是优先级低的永远获取其所需的资源)。
  • 资源调度可能导致线程切换
资源调度策略选择:
  • 一般情况下面首先非公平性的调度策略,其优点就是吞吐量达、能有效减少线程切换次数,但是缺点是资源申请者申请资源锁需要的偏差可能非常大,且会导致饥饿现象;在对资源的申请所需的时间偏差有所要求(及偏差较小)的情况下面,可以考虑使用公平性调度。
4、线程安全问题

线程安全:如果一个类在单线程环境下面能够正常的运行,并且在多线程环境下面,在其使用方不必为其做任何改动的情况下面也能正常运作,称其为线程安全,相应的称这个类具有线程安全性。
非线程安全:一个类在单线程环境能正常运作,但是在多线程环境下面不能正常运作,称为非线程安全。一个类如果能够发生竞态,那么这个类就是线程不安全的。不是线程安全的类,在多线程环境下面会产生线程安全问题。

  • 线程安全问题具体表现为三个方面:原子性、可见性、有序性。

你可能感兴趣的:(3、并发编程需要知道的一些基本概念)