2012-06-17 59 views
5

Tôi có một lớp được gọi là "Tài khoản"tại sao phương pháp đồng bộ hóa này không hoạt động như mong đợi?

public class Account { 

    public double balance = 1500; 

    public synchronized double withDrawFromPrivateBalance(double a) { 
     balance -= a; 
     return balance; 
    } 
} 

và một lớp được gọi là ATMThread

public class ATMThread extends Thread { 
    double localBalance = 0; 
    Account myTargetAccount; 

    public ATMThread(Account a) { 
     this.myTargetAccount = a; 
    } 

    public void run() { 
     find(); 
    } 

    private synchronized void find() { 
     localBalance = myTargetAccount.balance; 
     System.out.println(getName() + ": local balance = " + localBalance); 
     localBalance -= 100; 
     myTargetAccount.balance = localBalance; 
    } 

    public static void main(String[] args) { 
     Account account = new Account(); 
     System.out.println("START: Account balance = " + account.balance); 

     ATMThread a = new ATMThread(account); 
     ATMThread b = new ATMThread(account); 

     a.start(); 
     b.start(); 

     try { 
      a.join(); 
      b.join(); 
     } catch (InterruptedException ex) { 
      ex.printStackTrace(); 
     } 

     System.out.println("END: Account balance = " + account.balance); 
    } 

} 

tôi tạo ra hai chủ đề, chúng tôi giả có số dư ban đầu trong tài khoản ngân hàng (1500 $)

chuỗi đầu tiên cố gắng rút 100 đô la và chuỗi thứ hai.

Tôi hy vọng số dư cuối cùng là 1300, tuy nhiên đôi khi là 1400. Ai đó có thể giải thích cho tôi lý do không? Tôi đang sử dụng các phương pháp được đồng bộ hóa ...

Trả lời

12

Phương pháp này là đúng và nên được sử dụng:

public synchronized double withDrawFromPrivateBalance(double a) 
{ 
      balance -= a; 
      return balance; 
} 

Nó hạn chế một cách chính xác quyền truy cập vào tài khoản nội bộ nhà nước để chỉ một thread tại một thời điểm. Tuy nhiên lĩnh vực balance của bạn là public (vì vậy không thực sự nội bộ), đó là nguyên nhân gốc rễ của mọi vấn đề của bạn:

public double balance = 1500; 

Lợi dụng public modifier bạn đang truy cập nó từ hai chủ đề:

private synchronized void find(){ 
    localBalance = myTargetAccount.balance; 
    System.out.println(getName() + ": local balance = " + localBalance); 
    localBalance -= 100; 
    myTargetAccount.balance = localBalance; 
} 

Phương pháp này, mặc dù có vẻ chính xác với từ khóa synchronized, nó không phải là. Bạn đang tạo ra hai luồng và chủ đề synchronized về cơ bản là một khóa gắn với một đối tượng. Điều này có nghĩa là hai luồng này có khóa riêng và mỗi khóa có thể truy cập khóa riêng của nó.

Suy nghĩ về phương pháp withDrawFromPrivateBalance() của bạn. Nếu bạn có hai phiên bản của lớp Account thì an toàn để gọi phương thức đó từ hai luồng trên hai đối tượng khác nhau. Tuy nhiên, bạn không thể gọi số withDrawFromPrivateBalance() trên cùng một đối tượng từ nhiều hơn một luồng do từ khóa synchronized. Đây là loại tương tự.

Bạn có thể sửa chữa nó theo hai cách: hoặc sử dụng withDrawFromPrivateBalance() trực tiếp (lưu ý rằng synchronized không còn cần thiết ở đây):

private void find(){ 
    myTargetAccount.withDrawFromPrivateBalance(100); 
} 

hoặc khóa trên cùng một đối tượng trong cả hai chủ đề như trái ngược với khóa trên hai độc lập Thread các cá thể đối tượng:

private void find(){ 
    synchronized(myTargetAccount) { 
     localBalance = myTargetAccount.balance; 
     System.out.println(getName() + ": local balance = " + localBalance); 
     localBalance -= 100; 
     myTargetAccount.balance = localBalance; 
    } 
} 

Giải pháp thứ hai rõ ràng là kém hơn vì trước đây dễ quên đồng bộ hóa bên ngoài.Ngoài ra, bạn không bao giờ nên sử dụng các trường công cộng.

4

Đánh dấu phương thức đã đồng bộ lấy khóa trên đối tượng đang chạy, nhưng có hai đối tượng khác nhau ở đây: ATMThreadAccount.

Trong mọi trường hợp, hai khác nhau ATMThread s đang sử dụng khóa khác nhau, do đó ghi của chúng có thể trùng lặp và xung đột với nhau.

Thay vào đó, bạn phải đồng bộ hóa cả hai ATMThread s trên cùng một đối tượng.

8

Phương thức private synchronized void find() của bạn đang đồng bộ hóa trên các khóa khác nhau. Cố gắng đồng bộ hóa nó trên cùng một đối tượng như

private void find(){ 
    synchronized(myTargetAccount){ 
     localBalance = myTargetAccount.balance; 
     System.out.println(getName() + ": local balance = " + localBalance); 
     localBalance -= 100; 
     myTargetAccount.balance = localBalance; 
    } 
} 

Bạn cũng có thể làm cho lớp tài khoản của bạn chủ đề hơn an toàn, bằng cách làm cho các lĩnh vực của tư nhân và thêm synchronized modifier để tất cả getter và setter của nó và sau đó chỉ sử dụng này phương pháp để thay đổi giá trị của các trường.

Các vấn đề liên quan