此刻思量换成另一种方法来利用本章频繁见到的计数器。在下面的例子中,每个线程都包括了两个计数器,它们在run()里增值以及显示。除此以外,我们利用了Watcher类的另一个线程。它的浸染是监督计数器,查抄它们是否保持相等。这外貌是一项无意义的动作,因为假如查察代码,就会发明计数器必定是沟通的。但实际环境却不必然如此。下面是措施的第一个版本:
//: Sharing1.java // Problems with resource sharing while threading import java.awt.*; import java.awt.event.*; import java.applet.*; class TwoCounter extends Thread { private boolean started = false; private TextField t1 = new TextField(5), t2 = new TextField(5); private Label l = new Label("count1 == count2"); private int count1 = 0, count2 = 0; // Add the display components as a panel // to the given container: public TwoCounter(Container c) { Panel p = new Panel(); p.add(t1); p.add(t2); p.add(l); c.add(p); } public void start() { if(!started) { started = true; super.start(); } } public void run() { while (true) { t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); try { sleep(500); } catch (InterruptedException e){} } } public void synchTest() { Sharing1.incrementAccess(); if(count1 != count2) l.setText("Unsynched"); } } class Watcher extends Thread { private Sharing1 p; public Watcher(Sharing1 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 Sharing1 extends Applet { TwoCounter[] 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 = Integer.parseInt(getParameter("size")); numObservers = Integer.parseInt( getParameter("observers")); } s = new TwoCounter[numCounters]; for(int i = 0; i < s.length; i++) s[i] = new TwoCounter(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 Watcher(Sharing1.this); } } public static void main(String[] args) { Sharing1 applet = new Sharing1(); // 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("Sharing1"); aFrame.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e){ System.exit(0); } }); aFrame.add(applet, BorderLayout.CENTER); aFrame.setSize(350, applet.numCounters *100); applet.init(); applet.start(); aFrame.setVisible(true); } } ///:~
和往常一样,每个计数器都包括了本身的显示组件:两个文本字段以及一个标签。按照它们的初始值,可知道计数是沟通的。这些组件在TwoCounter构建器插手Container。由于这个线程是通过用户的一个“按下按钮”操纵启动的,所以start()大概被多次挪用。但对一个线程来说,对Thread.start()的多次挪用是犯科的(会发生违例)。在started标志和过载的start()要领中,各人可看到针对这一环境采纳的防御法子。
在run()中,count1和count2的增值与显示方法外貌上好像能保持它们完全一致。随后会挪用sleep();若没有这个挪用,措施便会堕落,因为那会造成CPU难于互换任务。
synchTest()要领采纳的好像是没有意义的动作,它查抄count1是否便是count2;假如不等,就把标签设为“Unsynched”(差异步)。可是首先,它挪用的是类Sharing1的一个静态成员,以便增值和显示一个会见计数器,指出这种查抄已乐成举办了几多次(这样做的来由会在本例的其他版本中变得很是明明)。
Watcher类是一个线程,它的浸染是为处于勾当状态的所有TwoCounter工具都挪用synchTest()。其间,它会对Sharing1工具中容纳的数组举办遍历。可将Watcher想象成它擦过TwoCounter工具的肩膀不绝地“偷看”。
Sharing1包括了TwoCounter工具的一个数组,它通过init()举办初始化,并在我们按下“start”按钮后作为线程启动。今后若按下“Observe”(调查)按钮,就会建设一个可能多个调查器,并对绝不设防的TwoCounter举办观测。
留意为了让它作为一个措施片在欣赏器中运行,Web页需要包括下面这几行:
<applet code=Sharing1 width=650 height=500> <param name=size value="20"> <param name=observers value="1"> </applet>
#p#分页标题#e#
可自行改变宽度、高度以及参数,按照本身的意愿举办试验。若改变了size和observers,措施的行为也会产生变革。我们也留意到,通过从呼吁行接管参数(可能利用默认值),它被设计成作为一个独立的应用措施运行。
下面才是最让人“不行思议”的。在TwoCounter.run()中,无限轮回只是不绝地反复相邻的行:
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
(和“睡眠”一样,不外在这里并不重要)。但在措施运行的时候,你会发明count1和count2被“调查”(用Watcher调查)的次数是不相等的!这是由线程的本质造成的——它们可在任何时候挂起(暂停)。所以在上述两行的执行时刻之间,有时会呈现执行暂停现象。同时,Watcher线程也正好跟从着进来,并正亏得这个时候举办较量,造成计数器呈现不相等的环境。
本例展现了利用线程时一个很是根基的问题。我们跟无从知道一个线程什么时候运行。想象本身坐在一张桌子前面,桌上放有一把叉子,筹备叉起本身的最后一块食物。当叉子要遇到食物时,食物却溘然消失了(因为这个线程已被挂起,同时另一个线程进来“偷”走了食物)。这即是我们要办理的问题。
有的时候,我们并不介怀一个资源在实验利用它的时候是否正被会见(食物在另一些盘子里)。但为了让多线程机制可以或许正常运转,需要采纳一些法子来防备两个线程会见沟通的资源——至少在要害的时期。
为防备呈现这样的斗嘴,只需在线程利用一个资源时为其加锁即可。会见资源的第一个线程会其加上锁今后,其他线程便不能再利用谁人资源,除非被解锁。假如车子的前座是有限的资源,高喊“这是我的!”的孩子会主张把它锁起来。