2012-11-10 22 views
16

मुझे आश्चर्य है कि निम्नलिखित उदाहरण में डेडलॉक से बचने के वैकल्पिक तरीके क्या हैं। निम्नलिखित उदाहरण एक सामान्य बैंक खाता है जो डेडलॉक समस्या को स्थानांतरित करता है। अभ्यास में इसे हल करने के लिए कुछ बेहतर दृष्टिकोण क्या हैं?डेडलॉक उदाहरण से बचें

class Account { 
    double balance; 
    int id; 
    public Account(int id, double balance){ 
      this.balance = balance; 
      this.id = id; 
    } 
    void withdraw(double amount){ 
      balance -= amount; 
    } 
    void deposit(double amount){ 
      balance += amount; 
    } 
} 
class Main{ 
    public static void main(String [] args){ 
      final Account a = new Account(1,1000); 
      final Account b = new Account(2,300); 
      Thread a = new Thread(){ 
       public void run(){ 
        transfer(a,b,200); 
       } 
      }; 
      Thread b = new Thread(){ 
       public void run(){ 
        transfer(b,a,300); 
       } 
      }; 
      a.start(); 
      b.start(); 
    } 
    public static void transfer(Account from, Account to, double amount){ 
      synchronized(from){ 
       synchronized(to){ 
        from.withdraw(amount); 
        to.deposit(amount); 
       } 
      } 
    } 
} 

मैं इसे गतिरोध मुद्दे का समाधान होगा अगर मैं नेस्टेड ताला बाहर मेरी हस्तांतरण विधि में की तरह निम्नलिखित

synchronized(from){ 
     from.withdraw(amount); 
} 
synchronized(to){ 
     to.deposit(amount); 
} 
+0

आपका उदाहरण क्लासिक डेडलॉक उदाहरण नहीं है। यह हमेशा एक ही थ्रेड के लिए दोनों खाते को लॉक करता है ताकि कोई डेडलॉक न हो। क्या आप इस कोड को अपनी स्थानांतरण विधि में आजमा रहे हैं: स्थैतिक शून्य हस्तांतरण (बैंक खाता, बैंक खाता से, डबल राशि) { सिंक्रनाइज़ (से) { \t से .withdraw (राशि); सिंक्रनाइज़ (से) { to.deposit (राशि); } } } – supernova

उत्तर

23

क्रमबद्ध खातों को अलग सोच रहा हूँ। मृत ताला खाते के क्रम से है (ए, बी बनाम बी, ए)।

तो कोशिश:

public static void transfer(Account from, Account to, double amount){ 
     Account first = from; 
     Account second = to; 
     if (first.compareTo(second) < 0) { 
      // Swap them 
      first = to; 
      second = from; 
     } 
     synchronized(first){ 
      synchronized(second){ 
       from.withdraw(amount); 
       to.deposit(amount); 
      } 
     } 
} 
+3

यह दो से अधिक खातों से निपटने के दौरान काम करेगा, सही? – peter

+0

मैं अवधारणा को अच्छी तरह से समझ नहीं सकता, लेकिन स्थिति के बारे में क्या है जिसमें दोनों खातों में समान संतुलन है? जहां तक ​​मैं समझता हूं, वे स्वैप नहीं किए जाएंगे, इसलिए डेडलॉक अभी भी मौजूद रहेगा। –

+3

@Piotr: नहीं, इस मामले में आप खातों को उनके लिए अद्वितीय कुछ प्रकार (जैसे खाता संख्या, या डीबी में उनकी प्राथमिक कुंजी इत्यादि) को सॉर्ट करते हैं। वास्तविक क्रम तब तक कोई फर्क नहीं पड़ता जब तक कि यह सभी प्रतिभागियों में स्थिर आदेश नहीं है (यानी आपके द्वारा सुझाए गए अनुसार कोई डुप्लिकेट नहीं है)। –

6

ताला समाधान के अलावा आदेश दिया आप भी कर सकते हैं किसी भी खाते स्थानान्तरण प्रदर्शन से पहले एक निजी स्थिर अंतिम ताला वस्तु पर सिंक्रनाइज़ करके गतिरोध बचा जाता है।

class Account{ 
double balance; 
int id; 
private static final Object lock = new Object(); 
    .... 




public static void transfer(Account from, Account to, double amount){ 
      synchronized(lock) 
      { 
        from.withdraw(amount); 
        to.deposit(amount); 
      } 
    } 

इस समाधान में समस्या है कि एक निजी स्थैतिक लॉक सिस्टम को क्रमशः "अनुक्रमिक रूप से" स्थानांतरित करने के लिए प्रतिबंधित करता है।

private final Lock lock = new ReentrantLock(); 




public static void transfer(Account from, Account to, double amount) 
{ 
     while(true) 
     { 
      if(from.lock.tryLock()){ 
      try { 
       if (to.lock.tryLock()){ 
        try{ 
         from.withdraw(amount); 
         to.deposit(amount); 
         break; 
        } 
        finally { 
         to.lock.unlock(); 
        } 
       } 
      } 
      finally { 
       from.lock.unlock(); 
      } 

      int n = number.nextInt(1000); 
      int TIME = 1000 + n; // 1 second + random delay to prevent livelock 
      Thread.sleep(TIME); 
     } 

} 

डेडलॉक क्योंकि ताले अनिश्चित काल के लिए आयोजित कभी नहीं रहे हैं इस दृष्टिकोण में नहीं होती है: यदि प्रत्येक खाता एक ReentrantLock है

एक और एक हो सकता है। यदि वर्तमान ऑब्जेक्ट लॉक अधिग्रहित किया गया है लेकिन दूसरा लॉक अनुपलब्ध है, तो पहला लॉक जारी किया जाता है और ताला लॉक को पुनः प्राप्त करने का प्रयास करने से पहले कुछ निश्चित समय के लिए सो जाता है।

+0

मैंने साक्षात्कार पर इस समाधान का आविष्कार किया जब सही जवाब नहीं पता था) – BrownFurSeal

+0

आपके पहले मामले में 'क्रमिक रूप से' क्या मतलब है? – peter

+0

एक धागा लॉक कंप्यूटिंग रखता है और दूसरा इंतजार कर रहा है। पहली बार नौकरी खत्म करने के बाद, दूसरा काम करेगा। दोनों समानांतर में काम नहीं किया था। – dreamcrash

8

यह एक क्लासिक सवाल है। मुझे दो संभावित समाधान दिखाई देते हैं:

  1. खातों को सॉर्ट करने और खाते में सिंक्रनाइज़ करने के लिए जिसकी आईडी किसी अन्य से कम है। यह विधि अध्याय 10 में प्रैक्टिस में समवर्ती जावा कंसुरेंसी के बाइबल में उल्लिखित है। इस पुस्तक में लेखकों ने खातों को अलग करने के लिए सिस्टम हैश कोड का उपयोग किया है। java.lang.System#identityHashCode देखें।
  2. दूसरा समाधान आपके द्वारा उल्लिखित है - हाँ आप नेस्टेड सिंक्रनाइज़ किए गए ब्लॉक से बच सकते हैं और आपका कोड डेडलॉक नहीं लेगा। लेकिन उस स्थिति में प्रसंस्करण में कुछ समस्याएं हो सकती हैं क्योंकि यदि आप पहले खाते से पैसे वापस लेते हैं तो दूसरे खाते को किसी भी महत्वपूर्ण समय के लिए बंद कर दिया जा सकता है और शायद आपको पहले खाते में पैसा वापस रखना होगा। यह अच्छा नहीं है और क्योंकि उस नेस्टेड सिंक्रनाइज़ेशन और दो खातों का ताला बेहतर और अधिक सामान्यतः उपयोग किया जाने वाला समाधान है।
-1

तीन आवश्यकताएँ हैं आप पूरा करना चाहिए:

  1. लगातार निर्धारित राशि के बाद एक खाते की सामग्री को कम करते हैं।
  2. निर्दिष्ट राशि से दूसरे खाते की सामग्री को लगातार बढ़ाएं।
  3. यदि उपरोक्त में से कोई एक सफल है, तो दूसरा भी सफल होना चाहिए।

आप Atomics का उपयोग करके 1. और 2. हासिल कर सकते हैं, लेकिन क्योंकि वहां कोई AtomicDouble आपको लगता है कि double अलावा कुछ का उपयोग करना होगा। AtomicLong शायद आपकी सबसे अच्छी शर्त होगी।

तो आपको अपनी तीसरी आवश्यकता के साथ छोड़ दिया गया है - यदि कोई अन्य सफल होता है तो सफल होना चाहिए। एक साधारण तकनीक है जो परमाणुओं के साथ शानदार काम करती है और यह getAndAdd विधियों का उपयोग कर रही है।

class Account { 
    AtomicLong balance = new AtomicLong(); 
} 

... 
Long oldDebtor = null; 
Long oldCreditor = null; 
try { 
    // Increase one. 
    oldDebtor = debtor.balance.getAndAdd(value); 
    // Decrease the other. 
    oldCreditor = creditor.balance.gtAndAdd(-value); 
} catch (Exception e) { 
    // Most likely (but still incredibly unlikely) InterruptedException but theoretically anything. 
    // Roll back 
    if (oldDebtor != null) { 
    debtor.getAndAdd(-value); 
    } 
    if (oldCreditor != null) { 
    creditor.getAndAdd(value); 
    } 
    // Re-throw after cleanup. 
    throw (e); 
} 
+0

परमाणु बाधित अपवाद नहीं फेंकते हैं। – BrownFurSeal

+0

सही! लेकिन जब मैं ओपी के पास जाता हूं तो मैं शर्त लगाता हूं कि खाता खाता/क्रेडिट डेबिट कुछ फेंक देगा। – OldCurmudgeon

3

आप प्रत्येक खाते (खाता वर्ग में) के लिए अलग लॉक भी बना सकते हैं और फिर लेनदेन करने से पहले दोनों ताले हासिल कर सकते हैं। एक नज़र डालें:

private boolean acquireLocks(Account anotherAccount) { 
     boolean fromAccountLock = false; 
     boolean toAccountLock = false; 
     try { 
      fromAccountLock = getLock().tryLock(); 
      toAccountLock = anotherAccount.getLock().tryLock(); 
     } finally { 
      if (!(fromAccountLock && toAccountLock)) { 
       if (fromAccountLock) { 
        getLock().unlock(); 
       } 
       if (toAccountLock) { 
        anotherAccount.getLock().unlock(); 
       } 
      } 
     } 
     return fromAccountLock && toAccountLock; 
    } 

दो ताले मिलने के बाद आप सुरक्षित के बारे में चिंता किए बिना स्थानांतरण कर सकते हैं।

public static void transfer(Acc from, Acc to, double amount) { 
     if (from.acquireLocks(to)) { 
      try { 
       from.withdraw(amount); 
       to.deposit(amount); 
      } finally { 
       from.getLock().unlock(); 
       to.getLock().unlock(); 
      } 
     } else { 
      System.out.println(threadName + " cant get Lock, try again!"); 
      // sleep here for random amount of time and try do it again 
      transfer(from, to, amount); 
     } 
    } 
0

यहां बताया गया समस्या का समाधान यहां दिया गया है।

import java.util.Random; 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 

public class FixDeadLock1 { 

    private class Account { 

     private final Lock lock = new ReentrantLock(); 

     @SuppressWarnings("unused") 
     double balance; 
     @SuppressWarnings("unused") 
     int id; 

     public Account(int id, double balance) { 
      this.balance = balance; 
      this.id = id; 
     } 

     void withdraw(double amount) { 
      this.balance -= amount; 
     } 

     void deposit(double amount) { 
      balance += amount; 
     } 
    } 

    private class Transfer { 

     void transfer(Account fromAccount, Account toAccount, double amount) { 
      /* 
      * synchronized (fromAccount) { synchronized (toAccount) { 
      * fromAccount.withdraw(amount); toAccount.deposit(amount); } } 
      */ 

      if (impendingTransaction(fromAccount, toAccount)) { 
       try { 
        System.out.format("Transaction Begins from:%d to:%d\n", 
          fromAccount.id, toAccount.id); 
        fromAccount.withdraw(amount); 
        toAccount.deposit(amount); 
       } finally { 
        fromAccount.lock.unlock(); 
        toAccount.lock.unlock(); 
       } 

      } else { 
       System.out.println("Unable to begin transaction"); 
      } 

     } 

     boolean impendingTransaction(Account fromAccount, Account toAccount) { 

      Boolean fromAccountLock = false; 
      Boolean toAccountLock = false; 

      try { 
       fromAccountLock = fromAccount.lock.tryLock(); 
       toAccountLock = toAccount.lock.tryLock(); 
      } finally { 
       if (!(fromAccountLock && toAccountLock)) { 
        if (fromAccountLock) { 
         fromAccount.lock.unlock(); 
        } 
        if (toAccountLock) { 
         toAccount.lock.unlock(); 
        } 
       } 
      } 

      return fromAccountLock && toAccountLock; 
     } 

    } 

    private class WrapperTransfer implements Runnable { 
     private Account fromAccount; 
     private Account toAccount; 
     private double amount; 

     public WrapperTransfer(Account fromAccount,Account toAccount,double amount){ 
      this.fromAccount = fromAccount; 
      this.toAccount = toAccount; 
      this.amount = amount; 
     } 

     public void run(){ 
      Random random = new Random(); 
      try { 
       int n = random.nextInt(1000); 
       int TIME = 1000 + n; // 1 second + random delay to prevent livelock 
       Thread.sleep(TIME); 
      } catch (InterruptedException e) {} 
      new Transfer().transfer(fromAccount, toAccount, amount); 
     } 

    } 

    public void initiateDeadLockTransfer() { 
     Account from = new Account(1, 1000); 
     Account to = new Account(2, 300);  
     new Thread(new WrapperTransfer(from,to,200)).start(); 
     new Thread(new WrapperTransfer(to,from,300)).start(); 
    } 

    public static void main(String[] args) { 
     new FixDeadLock1().initiateDeadLockTransfer(); 
    } 

} 
संबंधित मुद्दे