2013-06-22 7 views
5

使用している間、同期のは、私は非常に単純な<code>Bank Account</code>クラスを実装したいと仮定しましょう、と我々は同時実行とマルチスレッドの問題について世話をしたいのAtomicInteger

はそれを次の方法​​を作成することをお勧めしていてもbalanceAtomicIntegerですか?

一方、すべてのメソッドを同期させると、もうAtomicIntegerの使用はありません。そうですか?

import java.util.concurrent.atomic.AtomicInteger; 


public class Account { 
    AtomicInteger balance; 
    public synchronized int checkBalance(){ 
     return this.balance.intValue(); 
    } 
    public synchronized void deposit(int amount){ 
     balance.getAndAdd(amount); 
    } 
    public synchronized boolean enoughFund(int a){ 
     if (balance.intValue() >= a) 
      return true; 
     return false; 
    } 
    public synchronized boolean transfer_funds(Account acc, int amount){ // dest : acc 
     if (enoughFund(amount)){ 
      withdraw(amount); 
      acc.deposit(amount); 
      return true; 
     } 
     return false; 
    } 
    public synchronized boolean withdraw(int amount){ 
     if (checkBalance() < amount) 
      return false; 
     balance.getAndAdd(-1 * amount); 
     return true; 
    } 
} 

答えて

5

は、お使いの量は、(それが同期されていない場合)メソッドの実行の途中で横取りされてからスレッドを妨げないAtomicIntegerとして宣言されました。あなたの方法transfer_fundsは、どのような方法で同期されていない場合例えばだから、あなたはあなたの量がAtomicInteger

public /* synchronized */ boolean transfer_funds(Account acc, int amount){ // dest : acc 
     if (enoughFund(amount)){ 
      withdraw(amount); // <- thread can be preempted in the middle of method execution 
      acc.deposit(amount); 
      return true; 
     } 
     return false; 
    } 

このような問題は競合状態と呼ばれているだろうにもかかわらず、予期しない結果を得ることができます。 1つの可能性のある例は、2つのスレッドが同じアカウントから資金を移そうとする場合です。 1つのスレッドがクレジット転送を実行するためにenoughFundがあると判断すると、そのスレッドは先取りされ、同時に他のスレッドがこのアカウントから資金を移転することができます。最初のスレッドが再度処理を開始するときに、クレジット転送を実行するenoughFundsがあるかどうかを再確認しません(彼はすでにチェックしていますが、知識が古くなる可能性があります)が、次の実行行に進みます。この方法では、一貫した結果が得られない場合があります。すべての勘定科目の最初の総額を変更することができます。

Cay HorstmannのCore Javaブックでは、この点について非常によく説明されています。chapter about synchronizationは無料です。それはあなたが求めているのとほぼ同じ問題を詳細に記述します。

+0

したがって、AtomicIntegerを使用する必要はありませんか? – mr5

7

両方とも、同期させることをお勧めします。アトミックは必要ありません。

あなたは原子力の代わりに、同期に単純に依存している場合、あなたは、このようなこの方法のように問題を持つことができます。

if (enoughFund(amount)){ 
     withdraw(amount); 
     acc.deposit(amount); 
     return true; 
    } 

アトミックは、あなたの整数を意味同時アクセス、より安全であることを保証するのでenoughFund(amount)他のスレッドによって書き込まれている場合でも、amountの正しい値を提供することが保証されます。しかし、Atomicだけでは、この行で得られた値が次の行のコードと同じになることは保証されません。これは、他のスレッドがこれらの2つの行の間で別の原子操作を実行する可能性があります。その結果、withdraw(amount);は、ゼロ以下。

2

原子データ型はあなたを約束していることすべてが、その値にロックフリーが、スレッドセーフアクセスを与えることです。だから、あなたは​​上AtomicIntegerを使用する正当な理由の一つは、あなたが唯一この場合

synchronized (lockObj) { 
    myInt++; // slower than AtomicInteger 
} 

のようなあなたの更新操作を保護する必要がある場合、AtomicInteger.incrementAndGet()が速くなります。しかし、同期スコープがそれより大きく、インクリメントが一部に過ぎない場合は、​​ブロックを非アトミックな整数(ブロック内で保護されている)で使用することをお勧めします。

2

はい、正しいです。 AtomicIntegerは、オブジェクトへのすべてのアクセスが​​(最大で1つのスレッドのみが任意の瞬間にその内容にアクセスする)であれば、何の利益も与えません。

他の人が指摘したように、その変数へのスレッドセーフなアクセスが必要な場合は、AtomicIntegerの使用が最適です。
この場合、2つの化合物操作、transfer_fundsおよびwithdrawがあります。前者には3つのアクセス権があり、後者には2つのアクセス権があります。彼らは瞬時に発生しているかのよう

あなたはこれらの操作がアトミック自身、すなわちになりたい、彼らは他の人に見える、彼らは小さな操作で分解することはできません。これを達成するには、​​が必要です。


最後に、私は(おそらく)有用な提案を残したいと思います。 各アカウントに一意の識別子を割り当てる必要があります。デッドロックを防ぐために、どうしてあなたが求めるかもしれません。 とT2と、2つのアカウントa1a2の2つのアカウントがあるとします。

T1

a1.transfer_funds(a2, 42); 

T2:、両方のスレッドがお互いを待って無期限になってBを

T1 -> a1.enoughFund(42) 
T1 -> a1.withdraw(42) 
T2 -> a2.enoughFund(00101010) 
T2 -> a2.withdraw(00101010) 
T1 -> a2.deposit(42) // blocks on a2's monitor, because T2 already has it 
T2 -> a1.deposit(00101010) // same as above 

a2.transfer_funds(a1, 00101010); 

次のインタリーブが発生する可能性がありあなたのすべての方法は​​です。

溶液、割り当てる際に、各識別子をアカウント、例えば、次のようになります。

public class Account { 
    private int balance; 
    private final int id; 

    /* Not synchronized */ 
    public boolean transferFunds(Account acc, int amount) { 
     if (id < acc.getId()) { 
      synchronized (this) { 
       synchronized (acc) { 
        return transfer(acc, amount); 
       } 
      } 
     } 
     else if (id > acc.getId()) { 
      synchronized (acc) { 
       synchronized (this) { 
        return transfer(acc, amount); 
       } 
      } 
     } 
     return true; // same id, transfering to self has no effect. 
    } 

    private boolean transfer(Account acc, int amount) { 
     if (balance >= amount) { 
      balance -= amount; 
      // This is not synchronized, you may make it private. 
      acc.depositUnsynchronized(amount); 
      return true; 
     } 
     return false; 
    } 
} 

上記規則的にロック獲得を達成するので、どんなに場合は、すべてのスレッドが獲得しようとします最も低いIDを持つアカウント。そのアカウントで転送が行われている場合は、最初の転送が終了するまで他の転送は行われません。

+0

ここで、 'withdraw()'と 'checkBalance()'を同期させるだけです。 –

+0

もしあなたが 'AtomicInteger'を持っていれば、そうです。あなたのメソッドの 'balance'への参照を数えると、withdrawとtransferを除くすべてのメソッドが' balance'を一度参照し、そのアクセスの結果を処理することがわかります。たとえば、撤回には2つの参照(チェックと減算)があるため、同期なしでは安全ではありません(トム・アンダーソンの答えを記述しない限り)。 – afsantos

2

あなたが必死にAtomicIntegerを使用したい場合は、あなたが書くことができる:

public class Account { 
    private final AtomicInteger balance = new AtomicInteger(0); 

    public void deposit(int amount) { 
     balance.getAndAdd(amount); 
    } 

    public boolean withdraw(int amount) { 
     for (int i; i < SOME_NUMBER_OF_ATTEMPTS; ++i) { 
      int currentBalance = balance.get(); 
      if (currentBalance < amount) return false; 
      boolean updated = balance.compareAndSet(currentBalance, currentBalance - amount); 
      if (updated) return true; 
     } 
    } 

    public boolean transfer(int amount, Account recipient) { 
     boolean withdrawn = withdraw(amount); 
     if (withdrawn) recipient.deposit(amount); 
     return withdrawn; 
    } 
} 

安全だし、それはロックを使用しません。譲渡または撤退を行うスレッドは、これまでに完了することは保証されていませんが、ねえ。

比較セットの周りをループする手法が標準です。​​で使用されるロック自体が実装されている方法です。

関連する問題