【深入学习Java并发之一】并发学习总览

本文深入探讨并发编程的核心问题,包括线程安全、原子性、可见性和有序性,以及JMM如何确保这些属性。同时,详细介绍了Java提供的并发工具,如synchronized、volatile和JUC包中的高级组件,帮助读者理解并发程序设计。

一、 并发的主题和三个问题

  • 主题:线程安全

    我们到底要干什么

    从程序员的角度来讲,线程安全,即并发时必须保证多线程任务执行顺序的正确性。为了保证这个正确性:
    · JMM(Java Memory Model)提供了一些保障:happens-before、as-if-serial机制;
    · JMM提供了一些API:volatile、synchronized;
    · JDK也提供了一些并发包:JUC(Java Util Concurrent)。

    程序员在面对并发问题时,主要任务就是根据JVM和JDK提供的工具,对多个想要同时访问某资源的线程进行约束,从而保障并发的安全性。可以说,程序员编写并发程序,最重要的就是要保证并发安全。


    线程安全是怎么做到的

    到底怎么实现线程安全?应该用什么机制?换句话说,前面提到的我们应使用的工具,是怎么实现线程安全的?
    · 阻塞同步(如synchronized、AQS同步器等)
    · 非阻塞同步(一般采用CAS实现)
    · 无同步方案(ThreadLocal等)

    线程安全,说白了,就是希望各个线程按照顺序访问资源。那么最直接的思路,就是保证资源在同一时刻,只被一个线程占有,这就是同步;
    阻塞同步和非阻塞同步,逻辑上来说是一样的,只是实现的方式有所区别:阻塞同步通过让在资源抢占过程中失败的线程进行挂起,来保证同步,本质上是一种悲观锁的思想;而非阻塞同步则是通过循环CAS的方式,尝试是否有线程在占用资源,有就进行下一次尝试,没有就占用资源,本质上是一种乐观锁的思想。

    另外一个思路就是,回避并发。程序是由数据(meta data)和代码(code)组成的,线程安全保护是其中的数据。那么如果某方法不存在共享数据的访问,或者访问的数据可以在本线程内直接拿到,也算是解决了这个问题。


  • 三个问题:原子性、可见性和有序性

    什么是原子性、可见性和有序性,为什么要保证他们的实现

    这三个问题是对线程安全的细化要求
    · 原子性:类似于数据库里的“事务”的概念,强调一系列操作的不可分割性
    若某操作不具备原子性,那么在并发的环境中,它在执行的过程中有可能被其他操作打断,出现“操作到一半”的数据暴露在其他线程面前的情况,所以,线程安全的操作需要实现原子性。

    · 可见性:某线程对共享变量的修改须被其他线程得知。
    可见性是相对于JMM中“所有对变量的读写都必须在工作内存中完成”来说的,即正常情况下,线程间互相不知道各自对变量的更改。所以若想同步,某线程对共享变量的修改须被其他线程得知,即实现可见性。

    · 有序性:代码逻辑上的有序性。
    有序性是相对于指令重排序多线程并发访问的随机性来说的,正是这两个可能造成语句顺序混乱的机制,导致我们需要保证代码逻辑上的有序性,即实现有序性。


  • 重要的话

    在后面的讨论中,会对几乎所有并发工具进行讨论。讨论的主要思路就是

    • 它采用了哪种机制(阻塞/非阻塞同步/无同步)?
    • 它是否保证了上述三个性质?是怎么实现的?

二、JMM(Java Memory Model)对三个问题作出的保障

  • 有序性保障——happens-before与重排序、并发访问

    重排序
    为了提高程序处理性能 ,编译器和处理器可能会对代码执行顺序进行乱序执行:

      int i = 5;//1
      int k = 1;//2
      int j = 5;//3
      i = 10;   //4
      k= i;     //5
    

    在如上代码片中,1、2和3可以进行重排序。因为1和2都被赋值为了5,在机器码层面上,可以取出5这个值,依次赋给i和j,再执行给k赋为1的操作,这样少了一次5这个数字的取出操作。

    但是,并不是所有语句都可以乱序排序的,要依照JMM提供的happens-before机制。JMM同时也规定并保证了常用的几种happens-before的实现。


    happens-before
    happens-before很简单,就是在语句顺序可能被打乱的前提下,保证程序逻辑的正确性,即保证程序的有序性。
    换个角度,就是保证语句对其后的语句是可见的。换句话说,语句所造成的“影响”,应该被其后(可能受到它影响的)语句所得知。从这个角度来看,也保证了可见性

    比如上述代码片中,4与5就不可以进行重排序,因为语句5用到了语句4运行的结果,4的执行与否对5有“影响”。


    JMM 对 happens-before的一些支持

    • 程序顺序规则:一个线程中的操作 happens-before 其后任意操作
    • 监视器规则:monitor锁的解锁 happens-before 之后对此锁的加锁
    • volatile规则:volatile写 happens-before 其后对这个变量的volatile读
    • start()规则:线程A执行ThreadB.start() 则 start() happens-before 线程B中的任意操作
    • join()规则:线程A执行ThreadB.join() 则 B中任意操作 happens-before A从join语句中返回
    • 传递性:A happens-before B 且 B happens-before C 则 A happens-before C

    可以看出来,JMM对基本的程序逻辑有序进行了保证,也对sychronized和volatile关键字都进行了支持。

  • 可见性的逻辑支持

    JMM内存模型
    主内存:各线程公用,存储了所有变量。
    工作内存:各线程私有,保存着该线程可能用到的变量,拷贝自主内存。线程对变量的所有操作必须在工作线程内进行。

    这种内存模型,可以通过工作内存与主内存的数据交互,来完成数据可见性的实现,事实上,volatile就是这么做的(后面再说)。


  • 原子性的逻辑支持

    锁或循环CAS
    原子性的实现只能依靠锁(悲观/乐观)来实现,因为原子性要依赖于对资源的独占。


  • JVM提供的 两种并发方法及其三个问题的解决

    volatile
    【深入学习并发之二】volatile关键字详解
    synchronized
    【深入学习并发之三】synchronized关键字详解

三、JUC提供的并发工具及他们完成的工作

  • AQS(Abstract Queued Synchronizer)
    //链接坑位
  • CAS(Compare And Swap)
    //链接坑位
  • ReentrantLock
    //链接坑位
  • CountDownLatch
    //链接坑位
  • CyclicBarrier
    //链接坑位
  • Semaphore
    //链接坑位

四、集合类并发工具

//链接坑位

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值