加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

多线程基础

发布时间:2020-12-15 05:31:47 所属栏目:Java 来源:网络整理
导读:多线程基础 串行:一个线程顺序执行所有事务 并行:n个线程分别执行n个线程 并发:1个线程一个时间片段内交错执行所有事务 竞态:计算结果的正确性依赖相对时间顺序或线程的交错;多线程在没有采取任何措施(即当前线程执行时另一个线程进来读取)的情况下并

多线程基础

串行:一个线程顺序执行所有事务

并行:n个线程分别执行n个线程

并发:1个线程一个时间片段内交错执行所有事务

竞态:计算结果的正确性依赖相对时间顺序或线程的交错;多线程在没有采取任何措施(即当前线程执行时另一个线程进来读取)的情况下并发地更新,读取同一个共享变量(不包含局部变量),而产生脏读导致没有获取到最新更新的结果

? 产生条件:1.读-改-写 2.检测后行动

? 解决:原子性操作可以消除竞态的可能性,如synchronized关键字修饰方法

上下文切换:

pref命令监控上下文切换情况

处理器调度下,多个线程在一个时间只能有一个线程占用处理器,而这个线程只能在一个时间片内执行,处理器使用权结束后就暂停运行(切出),就换到下一个线程占用(切入),这个切入切出的过程就是上下文切换,上下文切换的时候为了保证线程下次能顺利执行,就需要在切出的时候保存相应进程的进度信息到内存中,下次被唤醒切入时恢复。

这个过程会涉及到线程运行过程访问变量的更新从主内存或缓存一致性从其他处理器加载到告诉缓存的时间消耗,或者高速缓冲中内容被冲刷到下级高速缓冲中的时间消耗

? 自发性上下文切换:自身原因,如调用sleep/yield/wait等

? 非自发性上下文切换:线程调度器切入一个优先级更高的线程,或者线程的时间片用完

线程调度:

处理器会维护一个等待队列,按照一个特定的调度策略调度线程占用资源

? 公平调度策略:先来后到顺序,保证每个线程都能执行,适用于资源占用时间占用较长,开销大

? 非公平调度策略:优先级顺序,切换次数少,减少上下文次数带来的开销,吞吐率较大,可能导致饥饿现象

线程安全问题:

  • 原子性

    • 定义

      原子性本意为不可分割,多线程环境下,针对同一组共享变量的原子操作操作不能被其他线程交错,这个动作要么都已经执行结束要么都尚未发生

    • 实现

      锁:具有排他特性的锁,可以保证同一时段只有一个线程可以操作共享变量

      CAS:CAS相当于硬件锁,是由处理器和内存实现的

    • 注意

      JLS规定:除long/double类型外的基础变量类型和引用类型的写操作都是原子性的,那么说明long/double在多线程并发访问时,可能会读取到某个线程更新的中间值,而不是最终值。TODO 为什么

    • 解决原子性问题

      volatile关键字修饰共享变量,保证写操作的原子性

      volatile关键字不保证如1.读-改-写 2.检测后行动等操作的原子操作,可使用synchronized关键字修饰方法以消除竞态产生的可能

  • 可见性

    • 定义

      一个线程对某个共享变量更新后,其他线程要能够立即读取到这个更新的结果,可见性只能保证读到相对新值而不是最新值

      父线程在启动子线程之前对变量的更新对于子线程来说是可见的

      Java平台下,一个线程终止后该线程对共享变量的更新对于调用该线程的join方法的线程而言是可见的

    • 可见性问题产生原因

      可见性的问题和计算机的存储系统有关,程序中的变量可能会被存储到:

      • 寄存器(处理器无法读取其他处理器的寄存器)

      • 主内存(即使共享变量存储在主内存,但也是通过高速缓存子系统访问的)

      • 写缓冲器(共享变量的更新还只到了写缓冲器,没有到高速缓存,而其他处理器上的线程是无法读取这个处理器上的写缓冲器)

      • 无效化队列(处理器可能将变量更新的通知结果通知给其他处理器时,其他处理器可能直接将通知放到无效化队列,而没有根据通知更新内容)

        等部件执行读写操作,这些部件相当于主内存的副本。

    • 解决(基于存储系统)

      可见性的保障是通过刷新处理器缓存和冲刷处理器缓存动作来实现的

      缓存同步:当线程更新了共享变量后,需要将更新的值写入到该处理器的高速缓存或主内存中(冲刷处理器缓存),然后其他处理器使用缓存一致性协议来读取该处理器的高速缓存的数据并更新到其高速缓存中

      Java平台保证可见性,可以使用volatile关键字:volatile关键字起到了提示变量可能被多线程共享,而阻止JIT编译器进行优化

      ? 1.读一个volatile关键字的变量可以保证处理器执行刷新处理缓存(缓存同步其他处理器更新结果)的动作

      ? 2.写一个volatile关键字的变量可以保证处理器执行冲刷处理器缓存(更新后写入到高速缓存或主内存而不是停留在写缓存)的动作

  • 有序性

    • 定义

      处理器A运行的一个线程所执行的内存访问操作在另外一个处理器B上运行的其他线程看起来是乱序的,即使它其实没有乱序

    • 有序性问题:乱序

      重排序

      ? 在多核处理器下,1.编译器可能改变代码操作的顺序性/2.处理器不是完全按照程序目标代码所指定的顺序执行/3.一个处理器上执行的多个操作,在其他处理器的角度看来,其执行顺序与目标代码指定的顺序不一致。原因:是对内存访问有关的操作,如读和写所作的优化;它能提升程序的性能,不影响单线程的正确性,但会影响多线程程序的正确性,产生安全问题;

      单处理器多线程环境,JIT编译器在运行期一个线程上发生的重排序不会影响这个处理器其他线程的正确性,但是编译期重排序是会产生影响正确性的,这里和线程的切入切出有关TODO

      • 指令重排序:是对指令的顺序做了调整;

        表现为:1. 源代码顺序与程序顺序不一致(编译器导致),2. 程序顺序与执行顺序不一致

        处理器乱序执行:处理器在执行指令前会动态调整指令的顺序,两种情况:1.哪条指令就绪了就执行哪条,2.猜测执行,但是指令执行的结果会按照相应指令被处理器读取的顺序提交到寄存器或内存中;虽然结果是顺序提交,但这还是对多线程有影响,对单线程没有影响

        以Java平台为例,Java代码编译器分为静态编译javac编译器(代码编译阶段:.java---->.class)和动态编译JIT编译器(代码运行阶段:.class------>JVM需要的机器码),因为编译器处于性能的考虑,只有动态编译器可能执行指令重排序,如对象初始化过程涉及到的引用赋值。

      • 存储子系统重排序

        (存储子系统=写缓冲器+高速缓存系统),在存储子系统的作用下,其他处理器对处理器A严格按照顺序执行几次内存访问的感知顺序与程序顺序不一致,这是一种现象而不是真正的动作,它的重排序对象是内存操作的结果

    • 解决

      有序性的保障:从逻辑上部分禁止重排序,从底层角度来说就是通过调用处理器提供相应的指令(内存屏障)来实现;

      如volatile和synchronized关键字

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读