小白初学Java的多线程编程,一下子遇到了海量的新知识点。一方面,Java中要想灵活使用多线程编程,需要了解concurrecy, Thread 等包和类中的很多实现的类的使用方法;另一方面,自己之前学习的比较多的JS是一个单线程的世界,很多多线程背后的原理我是不能理解的。所以这次的文章,用翻译的形式来细读一些基础的Java多线程编程的教程,顺便分享给大家,希望对各位能有帮助。
以下内容翻译自Thread Interference (The Java™ Tutorials > Essential Classes > Concurrency)
假设有一个简单的类叫做Counter
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
正常情况下,调用Counter 类中的方法 increment 会使c加1,调用decrement则会使c减1。
但如果一个Counter对象被多个线程所访问,就有可能产生线程间的干扰,导致出现不正确的结果。
当两个位于不同线程中的操作,交织(interleave)于同一个数据时,线程间干扰就发生了。也就是说,这两个操作本身是由N个步骤组成,这些步骤相互之间有交织。
表面上看来,对于Counter实例的这些操作是不可能产生交织的。但实际上,即使是看似简单的一个步骤,也会被虚拟机转换成多个步骤。例如,c++这个表达式就可以分解成以下3步:
- 获得当前c的值。
- 将c的值增加1。
- 将结果存储回c。
c--也可以通过同样的方式分解。
假设线程A和线程B在几乎同样的时间分别调用了increment和decrement方法,c的初值为0。则它们的交织后的动作,有可能变成下面这样:
- 线程A:获得c的值。
- 线程B:获得c的值。
- 线程A:增加c的值,结果为1。
- 线程B:减少c的值,结果为-1。
- 线程A:将结果存储回c,现在c的值为1。
- 线程B:将结果存储回c,现在c的值为-1。
线程A的调用产生的结果丢失,被线程B覆盖。上面这种操作交织,只是可能发生的一种情况。也有可能是B的结果丢失,或者最终得到正确的结果。因为这种不确定性,线程间干扰的debug变得困难。