线程安全

同步与安全

线程如果都各自干活互不搭理的话自然相安无事,但多数情况下线程直接需要打交道,而且需要分享共享资源,那么这个时候最核心的就是线程安全了。当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。

同步指的是线程之间的协作配合,以共同完成某个任务。在整个过程中,需要注意两个关键点:一是共享资源的访问,二是访问资源的顺序。通过前面的介绍,我们已经知道了如何让多个线程访问共享资源,但并没介绍如何控制访问顺序,才不会出现错误。如果两个线程同时访问同一内存地址的数据,一个写,一个读,如果不加控制,写线程只写了一半,读线程就开始读,必然读到的数据是错误的,不可用的,从而造成程序错误,这就造成了并发安全问题,为此我们必须要有一套控制机制来避免这样的事情发生。

线程安全

线程安全性是并发代码最重要也是最基本的要求,我们不应容忍大部分时候可以正确运行,但是在偶然情况下会出错的并发程序。多线程的核心矛盾即为竞态条件,即多个线程同时读写某个字段;而竞态条件下多线程争抢的资源就是竞态资源,或者说是临界资源临界区即是设计读写竞态资源的代码片。线程如果都各自干活互不搭理的话自然相安无事,但多数情况下线程直接需要打交道,而且需要分享共享资源,那么这个时候最核心的就是线程安全了。当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。

  • 无状态:这个有点函数式编程的味道,总之就是线程只有入参和局部变量,如果变量是引用的话,确保变量的创建和调用生命周期都发生在线程栈内,就可以确保线程安全。
  • 无共享状态:完全要求线程无状态比较难实现,必要的状态是无法避免的,那么我们就必须维护不同线程之间的不同状态,这可是个麻烦事。幸好我们有 ThreadLocal 这个神器,该对象跟当前线程绑定,而且只对当前线程可见,完美解决了无共享状态的问题。
  • 不可变状态:最后实在没办法避免状态共享,在线程之间共享状态,最怕的就是无法确保能维护好正确的读写顺序,而且多线程确实也无法正确维护好这个共享变量。那么我们索性粗暴点,把共享的状态定位不可变,比如价格 final 修饰一下,这样就达到安全状态共享。
  • 线程同步:锁、事件等,一个线程通常也不是所有步骤都需要共享状态,而是部分环节才需要的,那么我们把共享状态的代码拆开,无共享状态的那部分自然不用关心,而共享状态的小段代码,则通过加入消息组件来传递状态。这个设计到并发模式的流水线编程模式,下文并发模式会重点介绍。

上文已经提到,共享可变状态是造成线程不安全的唯一原因,那么为了解决线程安全性问题,可以先从避免共享状态或者避免可变状态入手。所有的线程安全性问题,都可以归结于同一个原因: 共享的可变状态。首先来看状态的共享,在 Java 中,如果类的某个域被声明为 public,或者通过 public 方法返回了某个 private 域的引用,那么这个域就可以被其它对象访问到,可以认为基于该类创建的对象,共享了其状态。一旦状态被共享,宿主对象就失去了状态的完全控制权,你无法预知其它对象会对共享状态做怎样的误操作。状态的可变性问题则来源于我们将在内存模型章节讨论的可见性、原子性与有序性问题。