当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步。像你所看到的,Java为此提供了独特的,语言水平上的支持。
同步的关键是管程(也叫信号量semaphore)的概念。管程是一个互斥独占锁定的对象,或称互斥体(mutex)。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定,它必须进入管程。所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。这些其他的线程被称为等待管程。一个拥有管程的线程如果愿意的话可以再次进入相同的管程。
如果你用其他语言例如C或C++时用到过同步,你会知道它用起来有一点诡异。这是因为很多语言它们自己不支持同步。相反,对同步线程,程序必须利用操作系统源语。幸运的是Java通过语言元素实现同步,大多数的与同步相关的复杂性都被消除。
你可以用两种方法同步化代码。两者都包括synchronized关键字的运用,下面分别说明这两种方法。
使用同步方法
Java中同步是简单的,因所有对象都有它们与之对应的隐式管程。进入某一对象的管程,就是调用被synchronized关键字修饰的方法。当一个线程在一个同步方法内部,所有试图调用该方法的同实例的其他线程必须等待。为了退出管程,并放弃对对象的控制权给其他等待的线程,拥有管程的线程仅需从同步方法中返回。
为理解同步的必要性,让我们从一个应该使用同步却没有用的简单例子开始。下面的程序有三个简单类。
首先是Callme,它有一个简单的方法call( )。call( )方法有一个名为msg的String参数。该方法试图在方括号内打印msg 字符串。有趣的是在调用call( ) 打印左括号和msg字符串后,调Thread.sleep(1000),该方法使当前线程暂停1秒。
下一个类的构造函数Caller,引用了Callme的一个实例以及一个String,它们被分别存在target 和 msg 中。构造函数也创建了一个调用该对象的run( )方法的新线程。该线程立即启动。
Caller类的run( )方法通过参数msg字符串调用Callme实例targetcall( ) 方法。最后,Synch类由创建Callme的一个简单实例和Caller的三个具有不同消息字符串的实例开始。
Callme的同一实例传给每个Caller实例。
// This program is not synchronized.
class Callme {
void call(String msg) {
System.out.print("[" + msg);
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println("Interrupted");
}
System.out.println("]");
}
}
class Caller implements Runnable {
String msg;
Callme target;
Thread t;
public Caller