2008-10-24 22 views
22

Nếu tôi tạo các lớp, được sử dụng tại thời điểm này chỉ trong một chủ đề duy nhất, tôi có nên làm cho chúng an toàn không, ngay cả khi tôi không cần nó vào lúc này? Có thể xảy ra, sau đó tôi sử dụng lớp này trong nhiều chủ đề, và vào thời điểm đó tôi có thể nhận được điều kiện đua và có thể khó có thể tìm thấy chúng nếu tôi không làm cho lớp an toàn ở nơi đầu tiên. Hay tôi nên làm cho lớp không an toàn cho luồng, để có hiệu suất tốt hơn? Nhưng tối ưu hóa sớm là điều ác.Tôi có nên luôn làm cho chuỗi an toàn java-code của mình hay vì lý do hiệu suất chỉ thực hiện khi cần?

Yêu cầu khác: Tôi có nên làm cho lớp của mình an toàn khi cần (nếu được sử dụng trong nhiều chủ đề, nếu không) hoặc tôi nên tối ưu hóa vấn đề này sau đó (nếu tôi thấy đồng bộ ăn phần quan trọng của thời gian xử lý)?

Nếu tôi chọn một trong hai cách, có phương pháp nào để giảm bớt bất lợi không? Hoặc tồn tại một khả năng thứ ba, rằng tôi nên sử dụng?

EDIT: Tôi đưa ra lý do khiến câu hỏi này nảy sinh trong tâm trí của tôi. Tại công ty chúng tôi, chúng tôi đã viết một quản lý người dùng rất đơn giản để ghi dữ liệu vào các tệp thuộc tính. Tôi đã sử dụng nó trong một ứng dụng web và sau khi một số công việc trên đó tôi đã nhận được các lỗi lạ, việc quản lý người dùng quên mất các thuộc tính của người dùng (bao gồm tên và mật khẩu) và các vai trò. Điều đó rất khó chịu nhưng không thể lặp lại một cách nhất quán, vì vậy tôi nghĩ đó là điều kiện đua. Vì tôi đã đồng bộ tất cả các phương thức đọc và ghi từ/trên đĩa, vấn đề đã biến mất. Vì vậy, tôi nghĩ, có lẽ tôi đã tránh được mọi rắc rối, nếu chúng tôi đã viết lớp với sự đồng bộ hóa ngay từ đầu?

CHỈNH SỬA 2: Khi tôi xem qua các mẹo của Lập trình viên thực dụng, tôi đã thấy mẹo # 41: Luôn thiết kế cho đồng thời. Điều này không nói rằng tất cả các mã nên được an toàn thread, nhưng nó nói rằng thiết kế nên có đồng thời trong tâm trí.

+0

Điều gì đó để giúp phân tích của bạn là không nghĩ đến việc đồng thời là tối ưu hóa sớm, mà là mối quan tâm về hiệu suất giống như BigO. Các quan sát tối ưu hóa ác là có liên quan đến những người bỏ vòng lặp hoặc chuyển đổi khó hiểu - nếu cấu trúc có điều kiện trong C/C++ không thể giải mã và duy trì, và có thể không mang lại cho bạn hiệu năng sau khi trình biên dịch thông qua mã (hoặc trong java thời gian chạy JIT). – reccles

Trả lời

19

Bắt đầu từ dữ liệu. Quyết định dữ liệu nào được chia sẻ rõ ràng và bảo vệ dữ liệu đó. Nếu có thể, hãy đóng gói khóa bằng dữ liệu. Sử dụng các bộ sưu tập đồng thời luồng an toàn có sẵn từ trước.

Bất cứ khi nào có thể, hãy sử dụng các đối tượng không thay đổi được. Đặt các thuộc tính cuối cùng, đặt giá trị của chúng trong các hàm tạo. Nếu bạn cần "thay đổi" dữ liệu, hãy cân nhắc trả về một cá thể mới. Các đối tượng không thể thay đổi không cần khóa.

Đối với các đối tượng không được chia sẻ hoặc bị giới hạn theo chủ đề, không dành thời gian làm cho chủ đề an toàn.

Ghi lại các kỳ vọng trong mã. Chú thích JCIP là lựa chọn được xác định trước tốt nhất hiện có.

+0

Tôi thích câu trả lời này. Bạn quyết định một cách (không đồng bộ hóa quá sớm), nhưng bạn cũng cung cấp một số giải pháp, điều đó sẽ giúp giảm nguy cơ gặp vấn đề. – Mnementh

+1

+1 để đề cập đến chú thích JCIP. –

0

Nếu bạn muốn theo dõi những gì Sun đã làm trong API Java, bạn có thể xem qua các lớp sưu tập. Nhiều lớp thu thập thông thường không an toàn với luồng nhưng có các đối tác an toàn theo luồng. Theo Jon Skeet (xem bình luận), nhiều lớp Java ban đầu an toàn, nhưng chúng không mang lại lợi ích cho các nhà phát triển, vì vậy một số lớp bây giờ có hai phiên bản - một phiên bản an toàn và luồng không an toàn.

Lời khuyên của tôi là không làm cho mã an toàn cho đến khi bạn phải làm như vậy, vì có một số chi phí liên quan đến an toàn chỉ. Tôi đoán điều này rơi vào cùng một danh mục như tối ưu hóa - đừng làm điều đó trước khi bạn phải làm.

+0

Trên thực tế, cách tiếp cận của Sun là vòng khác - các lớp sưu tập ban đầu (và StringBuffer) là an toàn chủ đề, sau đó họ nhận ra nó không thực sự giúp đỡ bất cứ ai, vì vậy khi họ thiết kế lại các lớp sưu tập họ làm cho chúng không an toàn . –

+0

Thật sao? Tôi sẽ sửa bài đăng của mình để đảm bảo lịch sử là đúng. Nhưng tôi biết rằng Sun đã làm nó vì lý do hiệu suất, đó là quan điểm của tôi. Cảm ơn. –

0

Thiết kế riêng các lớp để sử dụng từ nhiều chủ đề và tài liệu các lớp khác chỉ được sử dụng từ chuỗi đơn.

Các chuỗi đơn lẻ dễ làm việc hơn nhiều.

Tách logic đa luồng giúp đồng bộ hóa chính xác.

26

Tôi đã từng cố gắng làm cho mọi thứ đều an toàn - sau đó tôi nhận ra rằng ý nghĩa của "thread-safe" phụ thuộc vào cách sử dụng. Bạn thường không thể dự đoán việc sử dụng đó và người gọi sẽ để thực hiện hành động để sử dụng nó theo cách an toàn.

Những ngày này tôi viết hầu hết mọi thứ giả định luồng đơn, và đưa kiến ​​thức luồng vào một vài địa điểm mà nó quan trọng.

Có nói rằng, tôi cũng làm (nếu thích hợp) tạo các loại bất biến, vốn tự nhiên tuân theo đa luồng - cũng như dễ hiểu hơn về lý do nói chung.

+0

Bạn có một ví dụ, nơi mà việc sử dụng có thể phá vỡ thread-an toàn của một lớp học? – Mnementh

+7

Rất dễ dàng - thực hiện một bộ sưu tập trong đó mọi thao tác đơn lẻ đều an toàn theo luồng. Bây giờ lặp lại nó - bang, thread-an toàn đã biến mất. Bạn không muốn mỗi thao tác riêng lẻ lấy ra một khóa - bạn cần khóa cho toàn bộ thời gian bạn đang lặp lại. Bản thân bộ sưu tập không thể làm điều đó cho bạn. –

+0

OK, với lời giải thích này, bài đăng của bạn đáng giá cao. Cảm ơn. – Mnementh

6

Thực hiện theo cách đơn giản "càng đơn giản càng tốt, nhưng không đơn giản hơn". Không có yêu cầu, bạn không nên làm cho chúng an toàn. Làm như vậy sẽ là đầu cơ, và có thể không cần thiết. Lập trình an toàn cho chủ đề thêm nhiều phức tạp hơn cho các lớp của bạn và có thể sẽ làm cho chúng kém hiệu quả hơn do các tác vụ đồng bộ hóa.

Trừ khi được tuyên bố rõ ràng rằng đối tượng là an toàn theo luồng, kỳ vọng là nó không phải là.

1

Bạn hoàn toàn nên biết phân đoạn nào của mã của bạn sẽ là đa luồng và sẽ không.

Nếu không có khả năng tập trung khu vực đa luồng thành một phần nhỏ, có thể điều khiển được, bạn sẽ không thành công. Các phần của ứng dụng của bạn cần phải được phân tích kỹ lưỡng, phân tích đầy đủ, hiểu và thích nghi với môi trường đa luồng.

Phần còn lại không và do đó làm cho nó an toàn chỉ là một sự lãng phí.

Ví dụ, với giao diện đồ họa xoay, Sun đã quyết định rằng không ai trong số đó là đa luồng.

Ồ, và nếu ai đó sử dụng các lớp học của bạn - điều đó tùy thuộc vào họ để đảm bảo rằng nếu nó nằm trong một phần có ren thì hãy làm cho nó an toàn hơn.

Mặt trời ban đầu xuất hiện với bộ sưu tập chủ đề an toàn (chỉ). vấn đề là, threadsafe không thể được thực hiện un-threadsafe (cho mục đích hiệu suất). Vì vậy, bây giờ họ đi ra với phiên bản un-threadsafe với wrappers để làm cho chúng threadsafe. Đối với hầu hết các trường hợp, trình bao bọc là không cần thiết - giả sử rằng trừ khi bạn đang tự tạo chủ đề, lớp của bạn không phải là luồng an toàn - nhưng hãy viết nó trong javadocs.

0

"Luôn luôn" là một từ rất nguy hiểm trong phát triển phần mềm ... các lựa chọn như thế này là "luôn luôn" tình huống.

4

Cá nhân tôi sẽ chỉ thiết kế các lớp học "an toàn chỉ" khi cần - theo nguyên tắc tối ưu hóa chỉ khi cần. Sun dường như đã đi theo cùng một cách với ví dụ về các lớp bộ sưu tập luồng đơn.

Tuy nhiên có một số nguyên tắc tốt sẽ giúp bạn một trong hai cách nếu bạn quyết định thay đổi:

  1. quan trọng nhất: suy nghĩ trước khi bạn đồng bộ hóa. Tôi đã có một đồng nghiệp đã từng đồng bộ hóa nội dung "chỉ trong trường hợp - sau khi tất cả đồng bộ hóa phải tốt hơn, đúng không?" Đây là WRONG, và là nguyên nhân của nhiều lỗi bế tắc.
  2. Nếu đối tượng của bạn có thể bất biến, hãy biến chúng thành bất biến. Điều này sẽ không chỉ giúp phân luồng, sẽ giúp chúng được sử dụng một cách an toàn trong các bộ, vì các phím cho Bản đồ vv
  3. Giữ cho các đối tượng của bạn càng đơn giản càng tốt. Mỗi người nên lý tưởng chỉ làm một công việc. Nếu bạn có thể tìm thấy bạn có thể muốn đồng bộ hóa quyền truy cập vào một nửa số thành viên, thì có thể bạn nên chia đối tượng thành hai.
  4. Tìm hiểu java.util.concurrent và sử dụng nó bất cứ khi nào có thể. Mã của họ sẽ tốt hơn, nhanh hơn và an toàn hơn của bạn (hoặc của tôi) trong 99% các trường hợp.
  5. Đọc Concurrent Programming in Java, thật tuyệt vời!
+0

@Nick: Tôi rất muốn xem nhận xét của bạn rằng đồng bộ hóa đã gây ra nhiều lỗi bế tắc, bạn có cân nhắc việc tạo một câu hỏi kiểu nguy hiểm khi bạn hỏi làm thế nào một đoạn mã được đồng bộ có thể gây ra deadlocks và cung cấp câu trả lời? Nó sẽ rất thông tin để nghe về điều này. –

+0

@Nick: Vì một luồng không thể bế tắc đối với chính nó, ứng dụng bế tắc phải có nhiều luồng truy cập cùng một đối tượng. Vì vậy, một số đồng bộ hóa chắc chắn là bắt buộc. –

+0

Bạn nói đúng, tôi không nói đồng bộ là xấu - rõ ràng là không. Tôi đang nói Đồng bộ hóa mà không suy nghĩ là xấu. –

2

Tôi thấy chú thích JCIP rất hữu ích để khai báo lớp nào an toàn chỉ. Nhóm của tôi chú thích các lớp của chúng tôi là @ThreadSafe, @NotThreadSafe hoặc @Immutable.Điều này rõ ràng hơn nhiều so với việc phải đọc Javadoc và FindBugs cũng giúp chúng tôi tìm thấy vi phạm về hợp đồng @Immutable và @GuardedBy.

3

Giống như nhận xét bên: Đồng bộ hóa! = An toàn chủ đề. Mặc dù vậy bạn có thể không đồng thời sửa đổi dữ liệu, nhưng bạn có thể đọc đồng thời dữ liệu. Vì vậy, hãy nhớ Mô hình bộ nhớ Java trong tâm trí nơi đồng bộ hóa có nghĩa là làm cho dữ liệu đáng tin cậy có sẵn trong tất cả các chủ đề, không chỉ bảo vệ sửa đổi đồng thời của nó.

Và có, trong quan điểm của tôi-an toàn đã xây dựng ngay từ đầu và nó phụ thuộc vào logic ứng dụng nếu bạn cần xử lý đồng thời. Không bao giờ giả định bất cứ điều gì và ngay cả khi thử nghiệm của bạn có vẻ là tốt, điều kiện chủng tộc là chó ngủ.

0

Để tránh điều kiện chủng tộc, hãy khóa chỉ một đối tượng - đọc mô tả về điều kiện chủng tộc một cách tẻ nhạt và bạn sẽ khám phá ra rằng khóa chéo (điều kiện chủng tộc là một từ sai - cuộc đua dừng lại ở đó) luôn là hậu quả của hai chủ đề cố gắng khóa hai + đối tượng.

Làm cho tất cả các phương thức được đồng bộ hóa và thực hiện kiểm tra - đối với bất kỳ ứng dụng thực tế nào thực sự phải đối phó với sự cố đồng bộ hóa là một chi phí nhỏ. Những gì họ không nói với bạn là toàn bộ điều không khóa trên bảng con trỏ 16 bit ... tại thời điểm đó bạn đang uh, ...

Chỉ cần giữ cho bản lý lịch tiếp theo của bạn không bị gián đoạn.

0

Nếu tôi có thể tạo các lớp học, được sử dụng tại thời điểm này chỉ trong một chủ đề duy nhất, tôi nên làm cho họ thread-safe

Nó không phải là cần thiết cho một lớp được sử dụng bởi một thread để tự chỉ an toàn cho toàn bộ chương trình để đảm bảo an toàn cho luồng. Bạn có thể chia sẻ an toàn các đối tượng của các lớp không an toàn "chủ đề" giữa các chủ đề nếu chúng được bảo vệ bằng đồng bộ hóa thích hợp. Vì vậy, không cần phải làm cho một lớp tự an toàn cho đến khi điều đó trở nên rõ ràng.

Tuy nhiên, đa luồng là lựa chọn cơ bản (kiến trúc) trong chương trình. Đó là not really something to add as an after thought. Vì vậy, bạn nên biết ngay từ đầu mà lớp học cần phải được thread an toàn.

1

Dưới đây là cách tiếp cận cá nhân của tôi:

  • Hãy đối tượng và cấu trúc dữ liệu bất biến bất cứ nơi nào bạn có thể. Đó là thực hành tốt nói chung, và là chủ đề tự động an toàn. Đã giải quyết được sự cố.
  • Nếu bạn phải tạo một đối tượng có thể thay đổi thì thông thường đừng bận tâm cố gắng làm cho chủ đề an toàn.Lý do cho điều này là đơn giản: khi bạn có trạng thái có thể thay đổi thì khóa/điều khiển không thể được xử lý một cách an toàn bởi một lớp đơn lẻ. Ngay cả khi bạn đồng bộ hóa tất cả các phương pháp, điều này không đảm bảo an toàn luồng. Và nếu bạn thêm đồng bộ hóa vào một đối tượng mà chỉ bao giờ được sử dụng trong một ngữ cảnh một luồng, thì bạn vừa thêm phí không cần thiết. Vì vậy, bạn cũng có thể để nó cho người gọi/người dùng để thực hiện bất cứ hệ thống khóa nào là cần thiết.
  • Nếu bạn cung cấp API công khai cấp cao hơn thì triển khai bất kỳ khóa nào được yêu cầu để làm cho chuỗi API của bạn an toàn. Đối với chức năng cấp cao hơn, chi phí an toàn luồng là khá nhỏ, và người dùng của bạn chắc chắn sẽ cảm ơn bạn. Một API với ngữ nghĩa phức tạp mà người dùng cần phải làm việc xung quanh không phải là một API tốt!

Cách tiếp cận này đã phục vụ tôi tốt theo thời gian: bạn có thể cần phải thực hiện ngoại lệ không thường xuyên nhưng trung bình đó là một nơi rất tốt để bắt đầu!

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