thread interference
线程通信主要是通过分享对象域和域涉及的对象引用。这个形式的通信是极其有效率的。但是有可能导致两种错误发生:thread interference线程干扰和memory consistency errors内存一致性错误。防止这些错误发生的工具就是同步。
Thread Interfernce(线程干扰)
考虑一个简单的类: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对象被多个线程引用,线程间的干扰可能妨碍它像我们期望的那样子工作。
当运行在不同的线程但作用于相同的数据的两个操作交错时候,干扰会产生。这意味着这两个操作包含多个步骤,并且步骤的顺序是交叠的。对Counter的对象的操作泄漏了不太可能,因为对c的两个操作是单一的简单的语句。然而,每个简单语句会被虚拟机翻译成多个步骤。我们不检查虚拟机到底分了几个步骤,很明显可以看出 表达式c++可以分解为三个步骤:
1.取回当前c的值。
2..对取回的c值增加1
3.存储增加后的c的值
c--用同样的方法分解
假设线程A调用了increment方法,几乎同时线程B调用了decrement方法。如果c的初始值为0,两个线程交错的行为可能遵循下面的顺序:
1 Thread A: Retrieve c.
2 Thread B: Retrieve c.
3 Thread A: Increment retrieved value; result is 1.
4 Thread B: Decrement retrieved value; result is -1.
5 Thread A: Store result in c; c is now 1.
6 Thread B: Store result in c; c is now -1.
线程A的结果丢失,因为被线程B覆盖了。这个交错只是一种可能能。不同的环境下,可能是线程B的结果被线程A覆盖,或者一点错误没有。因为错误是不可预料的,检测和修正线程干扰的错误也是不同的。
Memory Consistency Errors
当不同的线程访问数据,本来个线程得到的数据应该是相同的,但却得到不同的。内存连贯性错误是复杂的。我们需要一个策略来避免此问题。
避免内存连贯性错误的关键要明白一个发生前关系。这个关系就是确保一个指定的语句所写的内存对另一个语句是可见的。为了明白这个关系,考虑下列示例。假设定义和初始化一个int型field:
int counter=0;
线程A和B共享了counter field。假设线程A增加了counter:
counter++;
然后,接下来,线程B打印出printer:
System.out.println(counter);
如果以上两个语句在同一个线程中运行,线程是安全的。假设打印出的counter值是1。但是如果这个两个语句在不同的线程执行,打印出来的counter值可能是0。因为这不能确保线程A对counter的改变对于线程B是可见的,除非编程者已经在这两个线程建立了一个发生前(happens-before)关系。
创建发生前关系有几个动作。其中一个是同步,像我们将要在下面看到的。
我们已经看到创建发生前关系的两个动作。
当一个语句调用Thread.start时,和这个语句有发生前关系的每一个语句,也有发生前关系和每一个执行了新线程的语句。创建了新线程的代码的效果对于这个新线程是可见的。
当一个线程到了临界期,并且引起Thread.join方法在另一个线程,然后返回。那么这个到了临界期的线程执行的所有语句和所有的随后join进来的语句建立了发生前关系。这个线程的代码的效果现在是可见的对于完成了join的线程。
synchronized methods
java语言提供了两种基本的同步语法:同步方法和同步语句块。
使一个方法同步,简单的在方法声明加上synchronized关键字。
public class SynchronizedCounter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public synchronized int value() { return c; } }
假设count是SynchronizedCounter的一个实例,那么让两个方法同步可以有以下效果:
第一,对于同一个对象上两个同步方法的调用是不可能交错的。当一个线程正在执行一个对象的同步方法时,所有调用此对象的其他线程是阻塞的,
知道这个线程处理完这个对象。
第二,当一个同步方法退出时候,它会自动与同一个对象的同步方法的连串的调用建立一个发生前的关系。这确保了对此对象状态的改变对所有线程
是可见的。
构建器是不能同步的,在构建器上用synchronized关键字是语法错误,同步构建器没有意义的。因为,正在在创建对象的时候,只有创建对象的线程
可以访问他。