background image

Java 并发编程:互斥

不同线程的操作在访问共享数据时,会因为交织进行而导致线程干扰和内存一致性错误。
大多数 Java 语句在编译成伪代码后都由多条虚拟机指令组成,这使它们有可能被其他线
程的语句所分割交织。不能分割交织的操作乘称作原子动作,这些动作一旦发生,便不能
在中途停止,要么完全发生,要么根本不发生,直至动作结束。前文所提到的++操作不
是一个原子动作。虽然大部分 Java 语句都不是原子动作,但是也有一些动作可以认定为
是原子性的:
  1.引用类型变量值的读和写。注意这儿是引用值的读写,而不是所引用对象内容的读
和写。
  2.除了 long 和 double 之外的简单类型的读和写。
  3.所有声明为 volatile 的变量的读和写,包括 long 和 double 类型以及引用类型
  原子动作是不能被交织分割的,因此可以放心使用,不用担心线程干扰问题。但注意
内存一致性错误对于原子动作仍然是存在的。使用 volatile 关键字能够减小内存一致性错
误发生的风险,任何对 volatile

变量的写操作和之后进行的读操作都会自动建立 发生

过 关系。这意味着任何对于 volatile 变量的改变都是对其他线程可见的。另外当某线程读
一个 volatile 变量时,它看到的不仅仅是对该变量的最新改动,也能看到这一改变带来
的副作用。
  使用原子变量访问要比使用互斥代码访问要高效得多,但是需要程序员人为地避免
内存一致性错误发生。是否需要额外措施避免这些错误往往取决于程序的规模和复杂度 。
java.util.concurrent 包中的类提供了不依赖于互斥原语的方法,在后面的文章我们将
逐步介绍。
  内部锁与互斥
  前面提到除少数原子动作能同时避免线程干扰和内存一致性错误外,其它操作都是
需要互斥保护才能避免错误的发生。这些保护技术在 Java 语言中通过互斥方法和互斥代
码实现。
  互斥访问机制是建立在内部锁的实体概念上的。API

规范通常称这种实体为 管程

(monitor)”。内部锁在这两个问题的解决上扮演着重要的角色,它为线程对对象的状态进

行强制排他性访问,并建立对于可视性至关重要的 发生过 关系。
  每个对象都有一个内部锁与其对应。如果一个线程需要排他一致性访问对象的字段,
它首先要在访问之前获得该对象的内部锁。当访问完成时需要释放该内部锁。线程在获得
该锁和释放该锁期间称作拥有该锁。一旦线程拥有内部锁,其他任何线程都不能再获得该
锁,它们在获得该锁时会被阻塞。

  当线程释放该内部锁时, 发生过 关系就在该动作和同把锁的后继动作之间建立起
来。
  互斥语句
  创建互斥性操作的方法是互斥语句。互斥语句的语法格式如下:
1 synchronized(lock){