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;
}
}
假设counter是SynchronizedCounter的一个实例,那么将其中的方法变为同步方法有两个影响:
- 首先,相同对象的两个同步方法的调用不可能交错。当一个线程在运行一个对象的某个同步方法时,其他也调用了此方法的线程必须阻塞,直到第一个线程完成工作。
- 其次,当退出一个同步方法时,它会和之后对该对象的该方法的调用自动建立一个happens-before关系。这保证了一个线程对对象状态的修改向其他线程可见。
注意,构造方法是不能同步的——使用synchronized方法修饰构造器,是一个语义错误。同步的构造方法是没有意义的,因为只有想要构造对象的线程,在构造对象时,才会去访问它。
警告:在构造一个需要共享的对象时,小心对该对象的引用没有过早地泄露出去。举个例子,你想要维护一个instances列表,其中包含所有class的实例。你可能很想往你的构造器中加入这行代码:
instance.add(this);
但这样一来,其他线程就可以使用instance,在对象创建完毕之前,来访问它。
同步方法保证了一种简单的防止线程混淆和内存不一致问题的策略:如果一个对象对超过1个线程可见,所有对该对象的读写操作都需要通过同步方法完成(除了非常特殊的final域,它被创建之后,就无法被修改;它在非同步方法中,也可以安全地读,只要这个对象已经被构造出来)。这个策略是有效的,但它也可能会导致活锁地问题,我们会在接下来的课程中看到。