第12章 Java内存模型与线程

第12章 Java内存模型与线程

  • 由于计算机的存储设备与处理器的运算速度有着几个数量级的差距,所以现代计算机系统都不得不加入一层或者多层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算需要使用的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了。
  • ”内存模型“一词,它可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。

12.3 Java内存模型

12.3.1 主内存和工作内存
  • Java内存模型的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。
  • Java内存模型规定了所有的变量都存储在主内存中(可与物理硬件的主内存类比,但物理上它仅是虚拟机内存的一部分)。每条线程还有自己的工作内存(可与前面讲的处理器高速缓存类比),线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取,赋值)都必须在工作内存中进行,而不能直接读写主内存中的数据不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
12.3.4 对于volatile型变量的特殊规则
  • 当一个变量被定义成volatile之后,它将具备两项特性:第一项是保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量不能做到这一点,普通变量的值在线程间传递时均需要通过主内存来完成。

  • ”基于volatile变量的运算在并发下是线程安全的“是不正确的。Java里面的运算操作符并非原子操作,这导致volatile变量的运算在并发下一样是不安全的。

  • 由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized,java.util.concurrent中的锁或原子类)来保证原子性:

  • 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

  • 变量不需要与其他的状态变量共同参与不变约束。

  • 使用volatile变量的第二个语义是禁止指令重排序优化,普通的变量仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序和程序代码中的执行顺序一致。

  • volatile变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢上一些,因为它需要在本地代码中插入许多屏障指令来保证处理器不发生乱序执行。不过即便如此,大多数场景下volatile的总开销仍然比锁来得更低。我们在volatile与锁中选择的唯一判断依据仅仅是volatile的语义能否满足使用场景的需求。

12.3.4 针对long和double型变量的特殊规则
  • 对于64位的数据类型(long和double),在模型中特别定义了一条宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位操作来进行,即允许虚拟机实现自行选择是否要保证64位数据类型的load,store,read和write这四个操作的原子性,这就是所谓的“long和double的非原子协定”
12.3.5 原子性,可见性与有序性
  1. 原子性
  2. 可见性
    可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存的,无论是普通变量还是volatile变量都是如此。普通变量和volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。
    除了volatile之外,Java还有两个关键字能实现可见性,它们是synchronized和final。同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store,write操作)“这条规则获得的。而final关键字的可见性是指:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把”this“的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到”初始化了一半“的对象),那么在其他线程中就能看见final字段的值。
  3. 有序性
12.3.6 先行发生原则
  • ”先行发生“的原则是判断数据是否存在竞争,线程是否安全的非常有用的手段。

  • ”先行发生“是Java内存模型中定义的两项操作之间的偏序关系,比如说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到,”影响“包括修改了内存中共享变量的值,发送了消息,调用了方法等。

  • 下面是Java内存模型下一些”天然的“先行发生关系,这些先行发生关系无须任何同步器协助就已经存在

    • 程序次序规则
    • 管程锁定规则
    • volatile变量规则
    • 线程启动规则
    • 线程终止规则
    • 线程中断规则
    • 对象终结规则
    • 传递性
  • 时间先后顺序与先行发生原则之间基本没有因果关系,所以我们衡量并发安全问题的时候不要受时间顺序的干扰,一切必须以先行发生原则为准

12.4 Java与线程

  • 目前线程是Java里面进行处理器资源调度的最基本单位。
  • 在Java类库API中,一个Native方法往往就意味着这个方法没有使用或无法使用平台无关的手段来实现
  • 实现线程主要有三种方式:使用内核线程实现(1:1实现),使用用户线程实现(1:N实现),使用用户线程加轻量级进程混合实现(N:M实现)
12.4.1 内核线程实现
  • 内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。
  • 轻量级进程(Light Weight Process.LWP),轻量级进程就是我们通常意义上所讲的线程。
12.4.2 用户线程实现
  • 用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援
12.4.3 混合实现
  • 线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现。
12.4.4 Java线程的实现
  • 商用Java虚拟机的线程模型普遍都被替换为基于操作系统原生线程模型来实现。
  • 操作系统支持怎么样的线程模型,在很大程度上会影响上面的Java虚拟机的线程是怎样映射的,这一点在不同的平台上很难达到一致。
12.4.5 Java线程调度
  • 线程调度是指系统为线程分配处理器使用权的过程,调度主要方式有两种,分别是协同式线程调度抢占式线程调度
  • 协同式线程调度,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上去。协同式多线程的最大好处是实现简单。它的坏处也很明显:线程执行时间不可控制,甚至如果一个线程的代码编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。
  • 抢占式线程调度,每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。例如在java中,有Thread::yield()方法可以主动让出执行时间,但是如果想要主动获取执行时间,线程本身是没有什么办法的。
  • Java使用的线程调度方式就是抢占式调度
  • 通过设置线程优先级可以”建议“操作系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点。Java语言一共设置了10个级别的线程优先级。
  • 线程优先级并不是一项稳定的调节手段。
12.4.6 状态转换
  • Java语言定义了6种线程状态,在任意一个时间点中,一个线程只能有且只有其中的一种状态,并且可以通过特定的方法在不同的状态之间转换。这6种状态分别是:
    • 新建(New):创建后尚未启动的线程处于这种状态
    • 运行(Runnable):包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着操作系统为它分配执行时间
    • 无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,它们要等待被其它线程显式唤醒。
    • 限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。
    • 阻塞:线程被阻塞了
    • 结束:已终止线程的线程状态,线程已经结束执行

12.5 Java与协程

12.5.2 协程的复苏
  • 内核线程的调度成本主要来自于用户态与核心态之间的状态转换,而这两种状态转换的开销主要来自于响应中断,保护和恢复执行现场的成本。
  • 物理硬件的各种存储设备和寄存器是被操作系统内所有线程共享的资源。
  • 由于最初多数的用户线程是被设计成协同式调度,所以它有了一个别名----“协程”。又由于这时候的协程会完整地做调用栈的保护,恢复工作,所以今天也被称为“有栈协程”。
  • 协程的主要优势是轻量
  • 协程当然也有它的局限,需要在应用层实现的内容(调用栈,调度器这些)特别多。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值