转载请标明出处:
Java中的同步
线程间的通讯首要的方式就是对字段及其字段所引用的对象的共享访问。这种通信方式是及其高效的,但是也是导致了可能的错误:线程间相互干涉和内存一致性的问题。避免出现这两种错误的方法就是同步。
线程间的相互干扰
—————————————————————————————————————————————————————————————————————————————
考虑下面的一个简单的类Counter:
class Counter { private int c = 0; public void increment() {c++;}public void decrement() {c–;}public int value() {return c;}}
不难看出,其中的()方法用来对
线程间的干扰出现在多个线程对同一个数据进行多个操作的时候,也就是出现了“交错”。这就意味着操作是由多个步骤构成的,而此时,在这多个步骤的执行上出现了叠加。
Counter
1.获取c的当前值。
2.对其当前值加1。
3.将增加后的值存储到c中。
表达式c–也是会被按照同样的方式进行翻译,只不过第二步变成了减
假定线程
1.线程
2.线程B:获取c的值。
3.线程
4.线程
5.线程
6.线程
这样线程
内存一致性错误
—————————————————————————————————————————————————————————————————————————————
内存一致性错误发生在不同线程对同一数据产生不同的“见解”。导致内存一致性错误的原因很负责,超出了本文的描述范围。庆幸的是,程序员并不需要知道出现这些原因的细节。我们需要的只是一种可以避免这种错误的方法。
避免出现内存一致性错误的关键在于理解“先后顺序”关系。这种关系是一种简单的方法,能够确保一条语句对内存的写操作对于其它特定的语句都是可见的。为了理解这点,我们可以考虑如下的示例。假定定义了一个简单的int类型的字段并对其进行了初始化:
intcounter=0;
该字段由两个线程共享:
counter++;
然后,线程
System.out.println(counter);
如果以上两条语句是在同一个线程中执行的,那么输出的结果自然是
我们可以采取多种方式建立这种“先后顺序”。使用同步就是其中之一,这点我们将会在下面的小节中看到。
到目前为止,我们已经看到了两种建立这种“先后顺序”的方式:
当一条语句中调用了Thread.start()方法,那么每一条和该语句已经建立了“先后顺序”的语句都和新线程中的每一条语句有着这种“先后顺序”。引入并创建这个新线程的代码产生的结果对该新线程来说都是可见的。当一个线程终止了并导致另外的线程中调用
关于哪些操作可以建立这种“先后关系”,更多的信息请参阅“”。
同步方法
—————————————————————————————————————————————————————————————————————————————
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关键字将导致语义错误。同步构造函数是没有意义的。这是因为只有创建该对象的线程才能调用其构造函数。
————————————————————————————————————————————————————————————————————————————
警告:在创建多个线程共享的对象时,要特别小心对该对象的引用不能过早地“泄露”。例如,假定我们想要维护一个保存类的所有实例的列表instances。我们可能会在构造函数中这样写到:
instances.add(this);
但是,其他线程可会在该对象的构造完成之前就访问该对象。
————————————————————————————————————————————————————————————————————————————
同步方法是一种简单的可以避免线程相互干扰和内存一致性错误的策略:如果一个对象对多个线程都是可见的,那么所有对该对象的变量的读写都应该是通过同步方法完成的(一个例外就是”问题。这点我们会在本课程的后面进行描述。
内部锁及同步
—————————————————————————————————————————————————————————————————————————————
同步是构建在被称为“内部锁或者是监视锁”的内部实体上的。(在API中通常被称为是“监视器”。)内部锁在两个方面都扮演着重要的角色:保证对对象访问的排他性和建立也对象可见性相关的重要的“先后顺序”。
它不同于旅游,那需要一个风景稍微漂亮的地方,