按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
} catch (InterruptedException e){}
}
}
public synchronized void synchTest() {
Sharing2。incrementAccess();
if(count1 != count2)
l。setText(〃Unsynched〃);
}
}
class Watcher2 extends Thread {
private Sharing2 p;
public Watcher2(Sharing2 p) {
this。p = p;
start();
}
public void run() {
while(true) {
for(int i = 0; i 《 p。s。length; i++)
p。s'i'。synchTest();
try {
sleep(500);
} catch (InterruptedException e){}
}
}
}
public class Sharing2 extends Applet {
TwoCounter2'' s;
private static int accessCount = 0;
private static TextField aCount =
new TextField(〃0〃; 10);
public static void incrementAccess() {
accessCount++;
aCount。setText(Integer。toString(accessCount));
}
private Button
start = new Button(〃Start〃);
observer = new Button(〃Observe〃);
private boolean isApplet = true;
private int numCounters = 0;
private int numObservers = 0;
public void init() {
if(isApplet) {
numCounters =
504
…………………………………………………………Page 506……………………………………………………………
Integer。parseInt(getParameter(〃size〃));
numObservers =
Integer。parseInt(
getParameter(〃observers〃));
}
s = new TwoCounter2'numCounters';
for(int i = 0; i 《 s。length; i++)
s'i' = new TwoCounter2(this);
Panel p = new Panel();
start。addActionListener(new StartL());
p。add(start);
observer。addActionListener(new ObserverL());
p。add(observer);
p。add(new Label(〃Access Count〃));
p。add(aCount);
add(p);
}
class StartL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i 《 s。length; i++)
s'i'。start();
}
}
class ObserverL implements ActionListener {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i 《 numObservers; i++)
new Watcher2(Sharing2。this);
}
}
public static void main(String'' args) {
Sharing2 applet = new Sharing2();
// This isn't an applet; so set the flag and
// produce the parameter values from args:
applet。isApplet = false;
applet。numCounters =
(args。length == 0 ? 5 :
Integer。parseInt(args'0'));
applet。numObservers =
(args。length 《 2 ? 5 :
Integer。parseInt(args'1'));
Frame aFrame = new Frame(〃Sharing2〃);
aFrame。addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e){
System。exit(0);
}
});
aFrame。add(applet; BorderLayout。CENTER);
aFrame。setSize(350; applet。numCounters *100);
applet。in it();
applet。start();
aFrame。setVisible(true);
505
…………………………………………………………Page 507……………………………………………………………
}
} ///:~
我们注意到无论run()还是 synchTest()都是“同步的”。如果只同步其中的一个方法,那么另一个就可以自
由忽视对象的锁定,并可无碍地调用。所以必须记住一个重要的规则:对于访问某个关键共享资源的所有方
法,都必须把它们设为 synchronized,否则就不能正常地工作。
现在又遇到了一个新问题。Watcher2 永远都不能看到正在进行的事情,因为整个run()方法已设为“同
步”。而且由于肯定要为每个对象运行run(),所以锁永远不能打开,而synchTest()永远不会得到调用。之
所以能看到这一结果,是因为accessCount 根本没有变化。
为解决这个问题,我们能采取的一个办法是只将run()中的一部分代码隔离出来。想用这个办法隔离出来的
那部分代码叫作“关键区域”,而且要用不同的方式来使用 synchronized关键字,以设置一个关键区域。
Java 通过“同步块”提供对关键区域的支持;这一次,我们用 synchronized关键字指出对象的锁用于对其
中封闭的代码进行同步。如下所示:
synchronized(syncObject) {
// This code can be accessed by on ly
// one thread at a time; assuming all
// threads respect syncObject's lock
}
在能进入同步块之前,必须在 synchObject 上取得锁。如果已有其他线程取得了这把锁,块便不能进入,必
须等候那把锁被释放。
可从整个run()中删除 synchronized关键字,换成用一个同步块包围两个关键行,从而完成对 Sharing2 例
子的修改。但什么对象应作为锁来使用呢?那个对象已由 synchTest()标记出来了——也就是当前对象
(this)!所以修改过的run()方法象下面这个样子:
public void run() {
while (true) {
synchronized(this) {
t1。setText(Integer。toString(count1++));
t2。setText(Integer。toString(count2++));
}
try {
sleep(500);
} catch (InterruptedException e){}
}
}
这是必须对 Sharing2。java 作出的唯一修改,我们会看到尽管两个计数器永远不会脱离同步(取决于允许
Watcher 什么时候检查它们),但在run()执行期间,仍然向 Watcher 提供了足够的访问权限。
当然,所有同步都取决于程序员是否勤奋:要访问共享资源的每一部分代码都必须封装到一个适当的同步块
里。
2。 同步的效率
由于要为同样的数据编写两个方法,所以无论如何都不会给人留下效率很高的印象。看来似乎更好的一种做
法是将所有方法都设为自动同步,并完全消除 synchronized关键字(当然,含有synchronized run()的例
子显示出这样做是很不通的)。但它也揭示出获取一把锁并非一种“廉价”方案——为一次方法调用付出的
代价(进入和退出方法,不执行方法主体)至少要累加到四倍,而且根据我们的具体现方案,这一代价还有
可能变得更高。所以假如已知一个方法不会造成冲突,最明智的做法便是撤消其中的 synchronized关键字。
14。2。3 回顾 Java Beans
我们现在已理解了同步,接着可换从另一个角度来考察Java Beans。无论什么时候创建了一个Bean ,就必须
假定它要在一个多线程的环境中运行。这意味着:
506
…………………………………………………………Page 508……………………………………………………………
(1) 只要可行,Bean 的所有公共方法都应同步。当然,这也带来了“同步”在运行期间的开销。若特别在意
这个问题,在关键区域中不会造成问题的方法就可保留为“不同步”,但注意这通常都不是十分容易判断。
有资格的方法倾向于规模很小(如下例的 getCircleSize())以及/或者“微小”。也就是说,这个方法调
用在如此少的代码片里执行,以至于在执行期间对象不能改变。如果将这种方法设为“不同步”,可能对程
序的执行速度不会有明显的影响。可能也将一个Bean 的所有public 方法都设为 synchronized,并只有在保
证特别必要、而且会造成一个差异的情况下,才将 synchronized关键字删去。
(2) 如果将一个多造型事件送给一系列对那个事件感兴趣的“听众”,必须假在列表中移动的时候可以添加
或者删除。
第一点很容易处理,但第二点需要考虑更多的东西。让我们以前一章提供的BangBean。java 为例。在那个例
子中,我们忽略了 synchronized 关键字(那时还没有引入呢),并将造型设为单造型,从而回避了多线程的
问题。在下面这个修改过的版本中,我们使其能在多线程环境中工作,并为事件采用了多造型技术:
//: BangBean2。java
// You should write your Beans this way so they
// can run in a multithreaded environment。
import java。awt。*;
import java。awt。event。*;
import java。util。*;
import java。io。*;
public class BangBean2 extends Canvas
implements Serializable {
private int xm; ym;
private int cSize = 20; // Circle size
private String text = 〃Bang!〃;
private int fontSize = 48;
private Color tColor = Color。red;
private Vector actionListeners = new Vector();
public BangBean2() {
addMouseListener(new ML());
addMouseMotionListener(new MM());
}
public synchronized int getCircleSize() {
return cSize;
}
public synchronized void
setCircleSize(int newSize) {
cSize = newSize;
}
public synchronized String getBangText() {
return text;
}
public synchronized void