2010-06-02 17 views
6

Giả sử chúng tôi có một lớp được gọi là AccountService quản lý trạng thái tài khoản.Cách tốt nhất để viết một phương thức cập nhật hai đối tượng trong môi trường java đa luồng?

AccountService được định nghĩa là

interface AccountService{ 
public void debit(account); 
public void credit(account); 
public void transfer(Account account, Account account1); 

} 

Với định nghĩa này, cách tốt nhất để thực hiện chuyển giao() để bạn có thể đảm bảo chuyển giao đó là một hoạt động nguyên tử là gì.

Tôi quan tâm đến câu trả lời mà tham khảo Java 1,4 mã cũng như câu trả lời rằng có thể sử dụng các nguồn lực từ java.util.concurrent trong Java 5

Trả lời

11

Đồng bộ hóa trên cả hai Account đối tượng và thực hiện chuyển nhượng. Đảm bảo bạn luôn đồng bộ hóa theo cùng thứ tự. Để làm như vậy, hãy thực hiện Account s triển khai Comparable, sắp xếp hai tài khoản và đồng bộ hóa theo thứ tự đó.

Nếu bạn không đặt các tài khoản, bạn chạy các khả năng bế tắc nếu một chuyển chủ đề từ A đến B và chuyển khác từ B đến A.

ví dụ chính xác này được thảo luận trên trang 207 của Java Concurrency in Practice, một cuốn sách quan trọng cho bất kỳ ai làm phát triển Java đa luồng. Đoạn mã ví dụ có sẵn từ publisher's website:

+0

+1 để tránh bế tắc – unbeli

+1

+1: Luôn khóa các đối tượng theo cùng thứ tự không thể bị nhấn quá mức. – Powerlord

+0

nếu hiệu suất không quan trọng, và có một AccountService đơn, người ta cũng có thể đơn giản là 'đồng bộ hóa' phương thức truyền. – aioobe

2

Bạn có thể cần phải có một giao dịch hỗ trợ đầy đủ (nếu đó là một ứng dụng thực tế của khóa học).

Khó khăn của giải pháp hầu như không phụ thuộc vào môi trường của bạn. Mô tả hệ thống của bạn một cách chi tiết và chúng tôi sẽ cố gắng giúp bạn (loại ứng dụng nào? Nó sử dụng máy chủ web? Máy chủ web nào? Cái gì được sử dụng để lưu trữ dữ liệu? Và vân vân)

+0

+1 để giải quyết các vấn đề không đồng bộ hóa với các giao dịch nguyên tử. –

0

không thể bạn tránh được việc phải đồng bộ hóa sử dụng một AtomicReference<Double> cho số dư tài khoản, cùng với get()set()?

+1

Bạn vẫn phải đồng bộ hóa trên các truy cập vào 'AtomicReference ', vì hoạt động' bal.set (bal.get() + creditAmt) 'không phải là nguyên tử: một cái gì đó có thể thay đổi ref được giữ bởi bal ở giữa cuộc gọi tới 'get()' và 'set()'. Nhưng nó là một vấn đề của buộc hai hoạt động (ghi nợ và tín dụng) vào một hoạt động nguyên tử, đặc biệt là nếu một trong những thất bại, người kia sẽ thất bại quá (rollback). –

+0

Đảm bảo tôi hiểu chính xác ... vấn đề tiềm ẩn khi sử dụng biến Nguyên tử mà không đồng bộ hóa không phải là những gì vợ tôi làm (thay đổi giá trị của số tiền giữa các cuộc gọi của tôi để nhận() và set()), nhưng thay vào đó, vợ cũ của tôi nào (thay đổi tham chiếu thành bal giữa các cuộc gọi của tôi để nhận() và set())? – Pete

+0

"giá trị" và "tham chiếu" rất khó sử dụng trong ngữ cảnh này vì bạn có tham chiếu ('AtomicReference - bal') để tham chiếu (' Double - bal.value') thành giá trị kép ('double - bal.value .value'). Tham chiếu 'bal' sẽ là cuối cùng, do đó, nó không phải là một vấn đề. Các vấn đề đồng bộ hóa đi vào làm toán, nơi bạn cần phải đầu tiên 1) 'get()' Double từ AtomicReference, 2) làm toán trên nó, và 3) 'set()' giá trị trở lại. Nếu bạn không thực hiện 1-2-3 nguyên tử, bạn có thể có Thread A làm 1 và 2, sau đó Thread B làm 1, sau đó Thread A làm 3, sau đó Thread B làm 2 và 3 giết chết những thay đổi của A. –

1

Nếu bạn có thể đảm bảo rằng tất cả các truy cập được thực hiện thông qua phương pháp chuyển, thì có lẽ cách tiếp cận dễ nhất là chỉ thực hiện chuyển một phương thức được đồng bộ hóa. Điều này sẽ được an toàn thread bởi vì điều này đảm bảo rằng chỉ có một sợi sẽ chạy phương thức truyền tải cùng một lúc.

Nếu các phương pháp khác cũng có thể truy cập vào AccountService, thì bạn có thể quyết định để tất cả các phương pháp này sử dụng một khóa toàn cầu duy nhất. Một cách dễ dàng để làm điều này là bao quanh tất cả các mã truy cập vào AccountService trong một khối (X) {...} được đồng bộ hóa trong đó X là một đối tượng chia sẻ/singleton (có thể là chính cá thể AccountService). Đây sẽ là luồng an toàn vì chỉ có một luồng sẽ truy cập vào AccountService cùng một lúc, ngay cả khi chúng nằm trong các phương thức khác nhau.

Nếu vẫn chưa đủ, thì bạn sẽ cần phải sử dụng các phương pháp khóa phức tạp hơn. Một cách tiếp cận phổ biến là khóa tài khoản riêng lẻ trước khi bạn sửa đổi chúng ... nhưng sau đó bạn phải rất cẩn thận để lấy khóa theo thứ tự nhất quán (ví dụ: bằng ID tài khoản) nếu không bạn sẽ gặp lỗi.

Cuối cùng nếu AccountService là dịch vụ từ xa thì bạn đang ở trong lãnh thổ khóa phân phối .... trừ khi bạn có bằng tiến sĩ về khoa học máy tính và số năm ngân sách nghiên cứu để ghi bạn có lẽ nên tránh đến đó.

+0

P.S. Để đơn giản và tính tổng quát, tôi sẽ dùng khóa toàn cầu cho đến khi bạn gặp vấn đề về hiệu năng/khả năng mở rộng (có thể không bao giờ!) Và chỉ sau đó bắt đầu nghĩ đến việc chuyển sang khóa cấp tài khoản. – mikera

+0

Tôi đánh giá cao sự hoàn chỉnh của câu trả lời của bạn. Những gì bạn đã đề cập trong bình luận của bạn chính xác là nơi tôi đang đứng đầu. Một khi bạn có để có được vào khóa cấp tài khoản có vẻ như có một nhu cầu cho một chương trình phức tạp hơn là chỉ đơn giản bằng cách sử dụng từ khóa 'đồng bộ'. –

+0

@DanielHonig: Đúng là tránh tối ưu hóa sớm, nhưng trong nhiều hoặc hầu hết các Hệ thống xử lý giao dịch (nơi công việc của máy cơ bản là chỉ làm việc này hàng ngày trên hàng triệu hồ sơ), nhà thiết kế sẽ không bao giờ cố gắng sử dụng một mutex toàn cầu. Chúng thường được thiết kế với nhiều lõi (nhiều!) Và cố gắng làm việc song song càng nhiều càng tốt. Vì vậy, thật hữu ích khi biết lý do khóa dựa trên tài khoản hữu ích trong miền này. –