java 并发

来源:互联网 发布:黑暗之魂淘宝 编辑:程序博客网 时间:2024/05/16 08:17

  • 概述
    • java并发两类知识
    • 一些概念
      • 同步
  • 提出问题 - 多线程导致了安全性问题
    • 理解线程
    • 整本书理解核心的基础
  • 分析问题 - 什么原因造成了多线程安全性问题竞态条件
    • 竞态条件
    • 结果空间
    • 人理解的原子 VS JVM识别的原子
  • 解决问题 - 加锁逻辑上实现我们期望的原子操作
    • 加锁机制
      • 不变性条件
      • 设定原子的规则
      • 内置锁
      • 重入
      • 如何用锁保护状态
  • 更大粒度的共享 - 对象的共享
  • 参考
    • 技术细节
    • 学习路线

本文章主要内容之摘自《java并发编程实战》,并且加入了一些自己的理解。若读者进一步探究,请阅读相关书籍。

概述

java并发两类知识

java并发需要学习的两大类知识:
(1)并发逻辑的设计
(2)线程本身的知识
  (java中一切皆对象,线程也建模成对象形式,但需要与其他对象进行区分,它负责程序的运行次序逻辑,而非业务逻辑

一些概念

同步

同步指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系。
哪些代码应该在一起连续执行?
哪些代码中间可以被打断,让线程执行其他代码?

1.提出问题 - 多线程导致了安全性问题

理解线程

在并发程序中,存在多个控制流(多个程序计数器)。而线程是控制流的载体,一个线程承载着一个控制流程(一个处于运行状态的流程)。

  多线程有很多好处(提高效率),但于此同时也带来了一些问题(线程安全性问题),作者通过简单的例子(先检查后执行的例子)展示了,多线程带来的结果的不确定性(静态条件)。

整本书理解核心的基础

【核心概念】线程安全问题产生前提:多线程 + 共享 + 共享可变

2.分析问题 - 什么原因造成了多线程安全性问题?(竞态条件)

竞态条件

竞态条件 :某个计算的结果的正确性取决于多个线程的交替执行时序。

结果空间

  上面提到了造成线程安全问题的前提: (多线程 + 共享 + 共享可变)但是,这只是造成线程安全问题的前提条件,这些因素造成的是结果的不确定性(可能的结果空间)。我们想要的正确结果也在多线程执行结果的结果空间之中,只不过得到正确结果的概率较低,且不再确定

人理解的原子 VS JVM识别的原子

  接下来,引出关键的问题:造成线程安全问题直接(接近程序员)原因?

原因在于,人缺乏对于指令原子性方面的控制

  为了说明原因,我们可以想象一种没有任何特殊处理的多线程场景(如下代码)。

  i++; // i为线程共享变量

  有两个线程A,B同时开始运行 这段代码(各运行一次)。我们期待的结果是 2  , 而真正可能存在的结果(结果空间)是 1,2 。(期待的结果在结果空间之中)
  
  为何会如此?原因在于i++可以分解为“读取-修改-写入”,也就是说这个我们看到的高级指令,到了JVM级别就被拆解为更小的单元了(原子操作),然后去交替执行。
  
  这是不符合我们的预期的,因为我们最初设计该多线程目的在于, 每个线程都能互斥的访问该语句,作用一次则加一次。换句话说,我们将 i++ 当作线程执行的原子操作了(但是计算机并不这么认为 ^_^)。 而事实上,它在执行过程中被拆解了(i++ 在JVM层面是一个复合操作),不是原子操作。

3.解决问题 - 加锁(逻辑上实现我们期望的原子操作)

  现在问题在于,如何让这段代码达到我们预期的目的,将 i++ 变为原子操作?

我们认为的原子操作(i++在线程一次执行完),
计算机认为是符合操作(“读取-修改-写入”)。

计算机认为复合操作通过加锁,具有我们期待的原子性

  在电脑硬件层面是存在原子操作的,但是硬件不能保证如此大粒度的原子操作。所以我们只能从逻辑上进行原子粒度控制(锁控制),什么叫做逻辑上控制原子粒度呢?

(1) lock与unlock是JVM的基本原子操作(我们此处认为其原子性由硬件保证),你不需要再去考虑其他因素,只要你写出来执行时候肯定是原子的。

  (2)但是 一段程序并不能在多线程执行时达到原子性,所以我们在逻辑上设置了该段代码进入的标志(lock( XX ),通过写进代码中)以及退出该段代码的标志(unlock)
  
  通过与(1)的原子性进行对比,我们可以发现,(1)中的lock是通过JVM(或者硬件)来保证的,但是(2)中的原子性则是通过在代码中嵌入lock(硬件保证的原子)等逻辑来保证的。

安全性是首位,其次在性能和简单性之间取得平衡。

加锁机制

不变性条件

通过不变性条件约束对象的状态,定义各种后验条件描述对象操作结果。
反映了各状态之间的约束关系

举例1】b = a +1; 可以看做不变性约束条件,当你更新a的同时,也必须同时更新b,为了满足a,b之间的关系(b = a+1)。

举例2】规定1-10岁为儿童,10-17为少年(不变性约束)。那么当一个人从8岁变为15岁之后,他的分类信息也应该变化(儿童变少年)。

设定原子的规则

保持状态的一致性,就需要在单个原子操作中更新所有相关的变量

内置锁

synchronized ( 以class对象作为锁 )(互斥锁:可能存在性能问题)

重入

如何用锁保护状态?

4.更大粒度的共享 - 对象的共享

   前面只是针对几个变量进行加锁,未涉及变量间的复杂依赖关系。接下来,文章将复杂度进行了提升,提升到了对象的共享层面(粒度变大,关系也变得复杂)来讨论并发问题。

java是面向对象的语言,所以关于涉及对象的并发操作是个不可回避的问题。

回顾(核心概念):

前面提到了造成线程安全性问题的3个前提条件(1)多线程;(2)共享;(3)共享可变

破坏条件】破坏这3个前提条件中任何一个条件,线程安全性问题将得意解决。下面也结合本章内容来进行一下说明。

更新中...

参考

技术细节

《深入浅出 Java Concurrency》目录
Java 8并发工具包漫游指南
cmsblogs-chenssy-记录、分享JAVA技术
Java并发编程 (海子的博客园)

学习路线

【CSDN :较为宏观】Java并发编程学习路线
【知乎:粒度更细】Java并发编程学习路线

原创粉丝点击