程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java如何共享資源

Java如何共享資源

編輯:關於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();
    }
  }
  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 = 
        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.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~


我們注意到無論run()還是synchTest()都是“同步的”。如果只同步其中的一個方法,那麼另一個就可以自由忽視對象的鎖定,並可無礙地調用。所以必須記住一個重要的規則:對於訪問某個關鍵共享資源的所有方法,都必須把它們設為synchronized,否則就不能正常地工作。
現在又遇到了一個新問題。Watcher2永遠都不能看到正在進行的事情,因為整個run()方法已設為“同步”。而且由於肯定要為每個對象運行run(),所以鎖永遠不能打開,而synchTest()永遠不會得到調用。之所以能看到這一結果,是因為accessCount根本沒有變化。
為解決這個問題,我們能采取的一個辦法是只將run()中的一部分代碼隔離出來。想用這個辦法隔離出來的那部分代碼叫作“關鍵區域”,而且要用不同的方式來使用synchronized關鍵字,以設置一個關鍵區域。Java通過“同步塊”提供對關鍵區域的支持;這一次,我們用synchronized關鍵字指出對象的鎖用於對其中封閉的代碼進行同步。如下所示:
 

synchronized(syncObject) {
  // This code can be accessed by only
  // 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關鍵字。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved