并发性
只有当要解决的问题需要一定程度的并发性时,程序员才会从多线程应用程序中受益。例如,如果打印队列应用程序仅支持一台打印机和一台客户机,则不应该将它编写为多线程的。一般说来,包含并发性的编码问题通常都包含一些可以并发执行的操作,同时也包含一些不可并发执行的操作。例如,为多个客户机和一个打印机提供服务的打印队列可以支持对打印的并发请求,但向打印机的输出必须是串行形式的。多线程实现还可以改善交互式应用程序的响应时间。
Synchronized 关键字
虽然多线程应用程序中的大多数操作都可以并行进行,但也有某些操作(如更新全局标志或处理共享文件)不能并行进行。在这些情况下,必须获得一个锁来防止其他线程在执行此操作的线程完成之前访问同一个方法。在 Java程序中,这个锁是通过 synchronized 关键字提供的。清单 1说明了它的用法。
清单 1. 使用 synchronized 关键字来获取锁
public class MaxScore {
intmax;
publicMaxScore() {
max = 0;
}
public synchronized void currentScore(int s) {
if(s> max) {
max = s;
}
}
public int max() {
return max;
}
}
这里,两个线程不能同时调用 currentScore()方法;当一个线程工作时,另一个线程必须阻塞。但是,可以有任意数量的线程同时通过max() 方法访问最大值,因为 max()不是同步方法,因此它与锁定无关。
试考虑在 MaxScore类中添加另一个方法的影响,该方法的实现如清单 2 所示。
清单 2. 添加另一个方法
public synchronized void reset() {
max = 0;
}
这个方法(当被访问时)不仅将阻塞 reset()方法的其他调用,而且也将阻塞 MaxScore 类的同一个实例中的currentScore()方法,因为这两个方法都访问同一个锁。如果两个方法必须不彼此阻塞,则程序员必须在更低的级别使用同步。清单3 是另一种情况,其中两个同步的方法可能需要彼此独立。
清单 3. 两个独立的同步方法
import java.util.*;
public class Jury {
Vectormembers;
Vectoralternates;
public Jury() {
members = new Vector(12, 1);
alternates = new Vector(12, 1);
}
public synchronized void addMember(String name) {
members.add(name);
}
public synchronized void addAlt(String name) {
alternates.add(name);
}
public synchronized Vector all() {
Vector retval = new Vector(members);
retval.addAll(alternates);
return retval;
}
}
此处,两个不同的线程可以将 members 和 alternates 添加到 Jury对象中。请记住,synchronized关键字既可用于方法,更一般地,也可用于任何代码块。清单 4中的两段代码是等效的。
清单 4. 等效的代码
synchronized void f() {
void f(){
// 执行某些操作
synchronized(this) {
}
// 执行某些操作
}
}
所以,为了确保 addMember() 和 addAlt()方法不彼此阻塞,可按清单 5 所示重写 Jury 类。
清单 5. 重写后的 Jury 类
import java.util.*;
public class Jury {
Vectormembers;
Vectoralternates;
public Jury() {
members = new Vector(12, 1);
alternates = new Vector(12, 1);
}
public void addMember(String name) {
synchronized(members) {
members.add(name);
}
}
public void addAlt(String name) {
synchronized(alternates) {
alternates.add(name);
}
}
public Vector all() {
Vector retval;
synchronized(members) {
retval = new Vector(members);
}
synchronized(alternates) {
retval.addAll(alternates);
}
return retval;
}
}
请注意,我们还必须修改 all() 方法,因为对 Jury对象同步已没有意义。在改写后的版本中,addMember()、addAlt() 和all() 方法只访问与 members 和 alternates 对象相关的锁,因此锁定Jury 对象毫无用处。另请注意,all() 方法本来可以写为清单 6所示的形式。
清单 6. 将 members 和 alternates 用作同步的对象
public Vector all() {
synchronized(members) {
synchronized(alternates) {
Vector retval;
retval = new Vector(members);
retval.addAll(alternates);
}
}
return retval;
}
但是,因为我们早在需要之前就获得 members 和 alternates的锁,所以这效率不高。清单 5中的改写形式是一个较好的示例,因为它只在最短的时间内持有锁,并且每次只获得一个锁。这样就完全避免了当以后增加代码时可能产生的潜在死锁问题。
同步方法的分解
正如在前面看到的那样,同步方法获取对象的一个锁。如果该方法由不同的线程频繁调用,则此方法将成为瓶颈,因为它会对并行性造成限制,从而会对效率造成限制。这样,作为一个一般的原则,应该尽可能地少用同步方法。尽管有这个原则,但有时一个方法可能需要完成需要锁定一个对象几项任务,同时还要完成相当耗时的其他任务。在这些情况下,可使用一个动态的“锁定-释放-锁定-释放”方法。例如,清单7 和清单 8 显示了可按这种方式变换的代码。
清单 7. 最初的低效率代码
public synchonized void doWork() {
unsafe1();
write_file();
unsafe2();
}
清单 8. 重写后效率较高的代码
public void doWork() {
synchonized(this) {
unsafe1();
}
write_file();
synchonized(this) {
unsafe2();
}
}
清单 7 和清单 8假定第一个和第三个方法需要对象被锁定,而更耗时的 write_file()方法不需要对象被锁定。如您所见,重写此方法以后,对此对象的锁在第一个方法完成以后被释放,然后在第三个方法需要时重新获得。这样,当write_file()方法执行时,等待此对象的锁的任何其他方法仍然可以运行。将同步方法分解为这种混合代码可以明显改善性能。但是,您需要注意不要在这种代码中引入逻辑错误。
嵌套类
内部类在 Java程序中实现了一个令人关注的概念,它允许将整个类嵌套在另一个类中。嵌套类作为包含它的类的一个成员变量。如果定期被调用的的一个特定方法需要一个类,就可以构造一个嵌套类,此嵌套类的唯一任务就是定期调用所需的方法。这消除了对程序的其他部分的相依性,并使代码进一步模块化。清单9,一个图形时钟的基础,使用了内部类。
清单 9. 图形时钟示例
public class Clock {
protected class Refresher extends Thread {
int refreshTime;
public Refresher(int x) {
super("Refresher/
欢迎您使用http://Blogmove.cn提供的"博客搬家"和"博文三窟"服务.