友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第87部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!



    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 页需要包含下面这几行:  

  

  

  

  

  

  

可自行改变宽度、高度以及参数,根据自己的意愿进行试验。若改变了size 和 observers,程序的行为也会 

发生变化。我们也注意到,通过从命令行接受参数(或者使用默认值),它被设计成作为一个独立的应用程 

序运行。  

下面才是最让人“不可思议”的。在TwoCounter。run()中,无限循环只是不断地重复相邻的行:  

t1。setText(Integer。toString(count1++));  

t2。setText(Integer。toString(count2++));  

 (和“睡眠”一样,不过在这里并不重要)。但在程序运行的时候,你会发现count1和 count2 被“观察” 

 (用Watcher 观察)的次数是不相等的!这是由线程的本质造成的——它们可在任何时候挂起(暂停)。所 

以在上述两行的执行时刻之间,有时会出现执行暂停现象。同时,Watcher 线程也正好跟随着进来,并正好 

在这个时候进行比较,造成计数器出现不相等的情况。  

本例揭示了使用线程时一个非常基本的问题。我们跟无从知道一个线程什么时候运行。想象自己坐在一张桌 

子前面,桌上放有一把叉子,准备叉起自己的最后一块食物。当叉子要碰到食物时,食物却突然消失了(因 

为这个线程已被挂起,同时另一个线程进来“偷”走了食物)。这便是我们要解决的问题。  

有的时候,我们并不介意一个资源在尝试使用它的时候是否正被访问(食物在另一些盘子里)。但为了让多 



                                                                                   502 


…………………………………………………………Page 504……………………………………………………………

线程机制能够正常运转,需要采取一些措施来防止两个线程访问相同的资源——至少在关键的时期。  

为防止出现这样的冲突,只需在线程使用一个资源时为其加锁即可。访问资源的第一个线程会其加上锁以 

后,其他线程便不能再使用那个资源,除非被解锁。如果车子的前座是有限的资源,高喊“这是我的!”的 

孩子会主张把它锁起来。  



14。2。2 Java 如何共享资源  



对一种特殊的资源——对象中的内存——Java 提供了内建的机制来防止它们的冲突。由于我们通常将数据元 

素设为从属于private (私有)类,然后只通过方法访问那些内存,所以只需将一个特定的方法设为 

synchronized (同步的),便可有效地防止冲突。在任何时刻,只可有一个线程调用特定对象的一个 

synchronized方法(尽管那个线程可以调用多个对象的同步方法)。下面列出简单的synchronized 方法:  

synchronized void f() { /* 。。。 */ }  

synchronized void g() { /* 。。。 */ }  

每个对象都包含了一把锁(也叫作“监视器”),它自动成为对象的一部分(不必为此写任何特殊的代 

码)。调用任何 synchronized方法时,对象就会被锁定,不可再调用那个对象的其他任何synchronized 方 

法,除非第一个方法完成了自己的工作,并解除锁定。在上面的例子中,如果为一个对象调用f(),便不能 

再为同样的对象调用g(),除非 f()完成并解除锁定。因此,一个特定对象的所有 synchronized方法都共享 

着一把锁,而且这把锁能防止多个方法对通用内存同时进行写操作(比如同时有多个线程)。  

每个类也有自己的一把锁(作为类的Class 对象的一部分),所以synchronized static 方法可在一个类的 

范围内被相互间锁定起来,防止与 static数据的接触。  

注意如果想保护其他某些资源不被多个线程同时访问,可以强制通过 synchronized 方访问那些资源。  

  

1。 计数器的同步  

装备了这个新关键字后,我们能够采取的方案就更灵活了:可以只为 TwoCounter 中的方法简单地使用 

synchronized关键字。下面这个例子是对前例的改版,其中加入了新的关键字:  

  

//: Sharing2。java  

// Using the synchronized keyword to prevent  

// multiple access to a particular resource。  

import java。awt。*;  

import java。awt。event。*;  

import java。applet。*;  

  

class TwoCounter2 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;  

  public TwoCounter2(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();  

    }  



                                                                                        503 


…………………………………………………………Page 505……………………………………………………………

  }  

  public synchronized void run() {  

    while (true) {  

      t1。setText(Integer。toString(count1++));  

      t2。setText(Integer。toString(count2++));  

      try {  

        sleep(500);  

      } 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   

  setBangText(String newText) {  

    text = newText;  

  }  

  public synchronized int getFontSize() {   

    return fontSize;   

  }  

  public synchronized void   

  setFontSize(int newSize) {  



                                                                                          507 


…………………………………………………………Page 509……………………………………………………………

    fontSize = newSize;  

  }  

  public synchronized Color getTextColor() {  

    return tColor;   

  }  

  public synchronized void   

  setTextColor(Color newColor) {  

    tColor = newColor;  

  }  

  public void paint(Graphics g) {  

    g。setColor(Color。black);  

    g。drawOval(xm cSize/2; ym cSize/2;   

      cSize; cSize);  

  }  

  // This is a multicast listener; which is  

  // more typically used than the unicast  

  // approach taken in BangBean。java:  

  public synchronized void addActionListener (  

      ActionListener l) {  

    actionListeners。addElement(l);  

  }  

  public synchronize
返回目录 上一页 下一页 回到顶部 10 9
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!