程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java多線程同步中的兩個特殊類

Java多線程同步中的兩個特殊類

編輯:關於JAVA

Java語言內置了synchronized關鍵字用於對多線程進行同步,大大方便了Java中多線程程序的編寫。但是僅僅使用synchronized關鍵字還不能滿足對多線程進行同步的所有需要。大家知道,synchronized僅僅能夠對方法或者代碼塊進行同步,如果我們一個應用需要跨越多個方法進行同步,synchroinzed就不能勝任了。在C++中有很多同步機制,比如信號量、互斥體、臨屆區等。在Java中也可以在synchronized語言特性的基礎上,在更高層次構建這樣的同步工具,以方便我們的使用。

當前,廣為使用的是由Doug Lea編寫的一個Java中同步的工具包,可以在這兒了解更多這個包的詳細情況:

http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html

該工具包已經作為JSR166正處於JCP的控制下,即將作為JDK1.5的正式組成部分。本文並不打算詳細剖析這個工具包,而是對多種同步機制的一個介紹,同時給出這類同步機制的實例實現,這並不是工業級的實現。但其中會參考Doug Lea的這個同步包中的工業級實現的一些代碼片斷。

本例中還沿用上篇中的Account類,不過我們這兒編寫一個新的ATM類來模擬自動提款機,通過一個ATMTester的類,生成10個ATM線程,同時對John賬戶進行查詢、提款和存款操作。Account類做了一些改動,以便適應本篇的需要:

import java.util.HashMap;
import java.util.Map;
class Account
{
  String name;
  //float amount;
  //使用一個Map模擬持久存儲
  static Map storage = new HashMap();
  static
  {
   storage.put("John", new Float(1000.0f));
   storage.put("Mike", new Float(800.0f));
  }
  public Account(String name)
  {
   //System.out.println("new account:" + name);
   this.name = name;
   //this.amount = ((Float)storage.get(name)).floatValue();
  }
  public synchronized void deposit(float amt)
  {
   float amount = ((Float)storage.get(name)).floatValue();
   storage.put(name, new Float(amount + amt));
  }
  public synchronized void withdraw(float amt)
   throws InsufficientBalanceException
   {
    float amount = ((Float)storage.get(name)).floatValue();
    if (amount >= amt) amount -= amt;
    else throw new InsufficientBalanceException();
    storage.put(name, new Float(amount));
   }
  public float getBalance()
  {
   float amount = ((Float)storage.get(name)).floatValue();
   return amount;
  }
}

在新的Account類中,我們采用一個HashMap來存儲賬戶信息。Account由ATM類通過login登錄後使用:

public class ATM
{
  Account acc;
  //作為演示,省略了密碼驗證
  public boolean login(String name)
  {
   if (acc != null) throw new IllegalArgumentException("Already logged in!");
   acc = new Account(name);
   return true;
  }
  public void deposit(float amt)
  {
   acc.deposit(amt);
  }
  public void withdraw(float amt) throws InsufficientBalanceException
  {
   acc.withdraw(amt);
  }
  public float getBalance()
  {
   return acc.getBalance();
  }
  public void logout ()
  {
   acc = null;
  }
}

下面是ATMTester,在ATMTester中首先生成了10個ATM實例,然後啟動10個線程,同時登錄John的賬戶,先查詢余額,然後,再提取余額的80%,然後再存入等額的款(以維持最終的余額的不變)。按照我們的預想,應該不會發生金額不足的問題。首先看代碼:

public class ATMTester
{
  private static final int NUM_OF_ATM = 10;
  public static void main(String[] args)
  {
   ATMTester tester = new ATMTester();
   final Thread thread[] = new Thread[NUM_OF_ATM];
   final ATM atm[] = new ATM[NUM_OF_ATM];
   for (int i=0; i<NUM_OF_ATM; i++)
   {
    atm[i] = new ATM();
    thread[i] = new Thread(tester.new Runner(atm[i]));
    thread[i].start();
   }
  }
  class Runner implements Runnable
  {
   ATM atm;
   Runner(ATM atm)
   {
    this.atm = atm;
   }
   public void run()
   {
    atm.login("John");
    //查詢余額
    float bal = atm.getBalance();
    try
    {
     Thread.sleep(1);
     //模擬人從查詢到取款之間的間隔
    }
    catch (InterruptedException e)
    { // ignore it }
     try
     {
      System.out.println("Your balance is:" + bal);
      System.out.println("withdraw:" + bal * 0.8f);
      atm.withdraw(bal * 0.8f);
      System.out.println("deposit:" + bal * 0.8f);
      atm.deposit(bal * 0.8f);
     }
     catch (InsufficientBalanceException e1)
     {
      System.out.println("余額不足!");
     }
     finally
     { atm.logout(); }
    }
   }
}

運行ATMTester,結果如下(每次運行結果都有所差異):

Your balance is:1000.0

withdraw:800.0

deposit:800.0

Your balance is:1000.0

Your balance is:1000.0

withdraw:800.0

withdraw:800.0

余額不足!

Your balance is:200.0

Your balance is:200.0

Your balance is:200.0

余額不足!

Your balance is:200.0

Your balance is:200.0

Your balance is:200.0

Your balance is:200.0

withdraw:160.0

withdraw:160.0

withdraw:160.0

withdraw:160.0

withdraw:160.0

withdraw:160.0

withdraw:160.0

deposit:160.0

余額不足!

余額不足!

余額不足!

余額不足!

余額不足!

余額不足!

為什麼會出現這樣的情況?因為我們這兒是多個ATM同時對同一賬戶進行操作,比如一個ATM查詢出了余額為1000,第二個ATM也查詢出了余額1000,然後兩者都期望提取出800,那麼只有第1個用戶能夠成功提出,因為在第1個提出800後,賬戶真實的余額就只有200了,而第二個用戶仍認為余額為1000。這個問題是由於多個ATM同時對同一個賬戶進行操作所不可避免產生的後果。要解決這個問題,就必須限制同一個賬戶在某一時刻,只能由一個ATM進行操作。如何才能做到這一點?直接通過synchronized關鍵字可以嗎?非常遺憾!因為我們現在需要對整個Account的多個方法進行同步,這是跨越多個方法的,而synchronized僅能對方法或者代碼塊進行同步。

我們首先開發一個BusyFlag的類,類似於C++中的Simaphore。

public class BusyFlag
{
  protected Thread busyflag = null;
  protected int busycount = 0;
  public synchronized void getBusyFlag()
  {
   while (tryGetBusyFlag() == false)
   {
    try
    {
     wait();
    }
    catch (Exception e) {}
   }
  }
  private synchronized boolean tryGetBusyFlag()
  {
   if (busyflag == null)
   {
    busyflag = Thread.currentThread();
    busycount = 1;
    return true;
   }
   if (busyflag == Thread.currentThread())
   {
    busycount++; return true;
   }
   return false;
  }
 
  public synchronized void freeBusyFlag()
  {
   if(getOwner()== Thread.currentThread())
   {
    busycount--;
    if(busycount==0)
    {
     busyflag = null;
     notify();
    }
   }
  }
  public synchronized Thread getOwner()
  {
    return busyflag;
  }
}

注:參考Scott Oaks & Henry Wong《Java Thread》

BusyFlag有3個公開方法:getBusyFlag, freeBusyFlag, getOwner,分別用於獲取忙標志、釋放忙標志和獲取當前占用忙標志的線程。使用這個BusyFlag也非常地簡單,只需要在需要鎖定的地方,調用BusyFlag的getBusyFlag(),在對鎖定的資源使用完畢時,再調用改BusyFlag的freeBusyFlag()即可。下面我們開始改造前面中的Account和ATM類,並應用BusyFlag工具類使得同時只有一個線程能夠訪問同一個賬戶的目標得以實現。首先,要改造Account類,在Account中內置了一個BusyFlag對象,並通過此標志對象對Account進行鎖定和解鎖:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
class Account
{
  String name;
  //float amount;
  BusyFlag flag = new BusyFlag();
  //使用一個Map模擬持久存儲
  static Map storage = new HashMap();
  static
  {
   storage.put("John", new Float(1000.0f));
   storage.put("Mike", new Float(800.0f));
  }
  static Map accounts = Collections.synchronizedMap(new HashMap());
  private Account(String name)
  {
   this.name = name;
   //this.amount = ((Float)storage.get(name)).floatValue();
  }
  public synchronized static Account getAccount (String name)
  {
   if (accounts.get(name) == null) accounts.put(name, new Account(name));
   return (Account) accounts.get(name);
  }
  public synchronized void deposit(float amt)
  {
   float amount = ((Float)storage.get(name)).floatValue();
   storage.put(name, new Float(amount + amt));
  }
  public synchronized void withdraw(float amt) throws InsufficientBalanceException
  {
   float amount = ((Float)storage.get(name)).floatValue();
   if (amount >= amt) amount -= amt;
   else throw new InsufficientBalanceException();
   storage.put(name, new Float(amount));
  }
  public float getBalance()
  {
   float amount = ((Float)storage.get(name)).floatValue(); return amount;
  }
  public void lock()
  {
   flag.getBusyFlag();
  }
  public void unlock()
  {
   flag.freeBusyFlag();
  }
}

新的Account提供了兩個用於鎖定的方法:lock()和unlock(),供Account對象的客戶端在需要時鎖定Account和解鎖Account,Account通過委托給BusyFlag來提供這個機制。另外,大家也發現了,新的Account中提供了對Account對象的緩存,同時去除了public的構造方法,改為使用一個靜態工廠方法供用戶獲取Account的實例,這樣做也是有必要的,因為我們希望所有的ATM機同時只能有一個能夠對同一個Account進行操作,我們在Account上的鎖定是對一個特定Account對象進行加鎖,如果多個ATM同時實例化多個同一個user的Account對象,那麼仍然可以同時操作同一個賬戶。所以,要使用這種機制就必須保證Account對象在系統中的唯一性,所以,這兒使用一個Account的緩存,並將Account的構造方法變為私有的。你也可以說,通過在Account類鎖上進行同步,即將Account中的BusyFlag對象聲明為static的,但這樣就使同時只能有一台ATM機進行操作了。這樣,在一台ATM機在操作時,全市其它的所有的ATM機都必須等待。

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