本系列文章根据《Java并发编程实战》整理

线程安全性问题

在没有充足同步的情况下,多个线程中的操作执行顺序是不可预测的,甚至会产生奇怪的结果。

1
2
3
4
5
6
7
public class UnsafeSequence{
private int value;
public int getNext(){
return this.value++;
}
}

UnsafeSequence的问题在于,由于执行时机不对,多个线程在调用getNext时会得到想同的值。value++看上去像单个操作,但是实际上它包含三个独立的操作,读取value,将value加1,并将结果写入value,由于运行是可能将多个线程之间的操作交易执行,因此这连个线程可能同时执行读操作,从而得到相同的值,并同时加1.结果则是在不同线程中返回了相同的结果
由于存在指令重排序的肯能,因此实际情况可能会更糟糕。

综上引起线程安全性的几个前提条件

  1. 多个线程的交替运行
  2. 执行非原子性的操作语句
  3. 编译器的指令重排序

由于多个线程要共享相同的内存地址,并且是并发执行,因此他们可能会访问或修改其他线程正在使用的变量,虽然给线程间通信带来了数据共享的便利,但是给多线程执行带来了不可预测的结果,要想多线程的行为可预测,必须对共享变量的操作进行同步。

1
2
3
4
5
6
7
public class UnsafeSequence{
private int value;
public synchronized int getNext(){
return this.value++;
}
}

如果没有同步,那么无论是编译器,硬件还是运行时,都可以随意安排操作的执行顺序和时间,例如对寄存器或者处理器中的变量缓存,而这些被缓存的变量对于其他线程来说是暂时不可见的。虽然这些技术可以实现更优的性能,但是给开发人员带来了负担

线程性能问题

在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁的出现上下文切换的操作,这种操作带来了极大的开销,保存和恢复上下文,丢失局部性,并且CPU将更多的事件花在调度上而不是线程上面。当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存取中的数据无效,以及增加共享内存总线的同步流量。这些因素都将带来额外的性能开销

在使用框架时保证代码的并发安全性

框架通过在框架线程中调用应用程序代码并将并发性引入到程序中,在代码中不可避免的访问应用程序状态,因此所有访问这些状态的代码路径都必须是线程安全的。