介绍

在早期的分时共享系统中,每个进程都是一个虚拟的冯诺依曼机。他们拥有一个内存空间,存储着指令和数据,根据机器语言的语义来顺序的执行指令。
线程允许程序控制流的多重分支同时存在于一个进程中,他们共享进程范围内的资源。线程有些时候也被称为轻量级进程。并且大多数现代操作系统把线程作为时序调度的基本单元。

  • 线程的优点
    线程能通过把异步的工作流程转化为普遍存在的顺序流程,使得程序更容易读写和维护。在多处理器的硬件环境中,使用多线程可以更加有效的使用硬件的资源。

  • 线程的风险
    在没有充分同步的情况下,多线程中的各种操作的顺序是不可预测的。
    常见的并发危险还有竞争条件。因为线程共享相同的内存地址空间,并发的运行,他们可能访问或修改其它的线程正在使用的变量。
    在缺少同步的时候,编译器,硬件和运行时事实上对时间和活动顺序是很随意的,比如在寄存器或高速缓冲中的存储变量,这样会使得他们对于其它线程暂时不可见的。它能提高性能,但是给开发带来了困难。
    活跃度失败的风险,当一个活动进入某种它永远无法继续再执行的状态时,活跃度失败就会发生了。活跃度失败有多种形式,如死锁(deadlock)、饥饿(starvation)、活锁(livelock)。
    多线程程序还存在性能危险。

线程安全

无论何时,只要有多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。

当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步以及在调用方代码不必作其它的协调,这个类的行为仍然时正确的,那么称这个类时线程安全的。

对线程安全类的实例进行顺序或并发的一系列操作,都不会导致实例处于无效状态。
无状态对象永远是线程安全的。

无状态对象和有状态对象

有状态对象,就是有实例变量的对象,可以保存数据。
无状态对象,就是没有实例变量的对象,不能保存数据,是不变类。

原子性

原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败。即在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程所干扰。

java内存模型定义的原子操作

  • lock(锁定):作用于主内存中的变量,它把一个变量标识为一个线程独占的状态;
  • unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便后面的load动作使用;
  • load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本
  • use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
  • assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;
  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用;
  • write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

竞争条件

类中存在竞争条件,导致其结果是不可靠的。
常见的一种竞争条件是“检查再运行”,使用一个潜在的过期值作为决定下一步操作的依据。

检查再运行的常见用法是惰性初始化,它导致其不是线程安全的,如懒汉式单例模式:

1
2
3
4
5
6
7
8
9
10
11
@NotThreadSafe
public class LazyInitRace{
private ExpensiveObject instance=null;

public ExpensiveObject getInstance(){
if(instance==null){
instance=new ExpensiceObject();
}
return instance;
}
}

java.uiil.concurrent.atomic包种包括了原子变量类,这些类用来实现数字或对象应用状态转换。

内部锁

java提供了强制原子性的内置锁机制:synchronized块,其包含锁对象的引用,以及这个锁保护的代码块。内部锁在java中扮演了互斥锁的角色,同一时间至多有一个线程拥有锁。因此,由同一个锁保护的synchronized块会各自原子地执行,不会相互干扰。
内部锁是可重入锁。当一个线程请求其它线程已经占有的锁时,请求线程将被阻塞,因为内部锁时可重进入的,因此线程企图获得它自己占有的锁时,请求会成功。

1
2
3
4
5
6
7
8
9
10
11
public class Widget{
public synchronized void doSomething(){
...
}
}
/*因为内部锁是可重进入的,所以不会发生死锁*/
public class LoggingWidget extend Widget{
public synchronized void doSomething(){
super.doSomething();
}
}