Vấn đề tương tranh thường xuyên nhất bạn gặp phải trong Java là gì? [đóng cửa]


191

Đây là một cuộc thăm dò ý kiến ​​về các vấn đề tương tranh phổ biến trong Java. Một ví dụ có thể là tình trạng bế tắc hoặc tình trạng đua cổ điển hoặc có lẽ là lỗi phân luồng EDT trong Swing. Tôi quan tâm đến cả hai vấn đề có thể xảy ra nhưng cũng là vấn đề phổ biến nhất. Vì vậy, vui lòng để lại một câu trả lời cụ thể về lỗi đồng thời Java cho mỗi bình luận và bỏ phiếu nếu bạn thấy lỗi bạn gặp phải.


16
Tại sao điều này bị đóng cửa? Điều này hữu ích cho cả các lập trình viên khác đang cầu xin đồng thời trong Java và để có ý tưởng về các lớp khiếm khuyết đồng thời nào đang được các nhà phát triển Java khác quan sát nhiều nhất.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

@Longpoke Thông báo đóng cửa giải thích lý do tại sao nó đóng. Đây không phải là một câu hỏi với một câu trả lời "chính xác" cụ thể, nó là một câu hỏi thăm dò ý kiến ​​/ danh sách. Và Stack Overflow không có ý định lưu trữ các loại câu hỏi này. Nếu bạn không đồng ý với chính sách đó, bạn có thể muốn thảo luận về nó trên meta .
Andrzej Doyle

7
Tôi đoán cộng đồng không đồng ý vì bài viết này nhận được hơn 100 lượt xem / ngày! Tôi thấy nó rất hữu ích khi tôi tham gia vào việc phát triển một công cụ phân tích tĩnh được thiết kế đặc biệt để khắc phục các sự cố đồng thời contemplateltd.com/threadsafe . Có một ngân hàng có các vấn đề tương tranh thường gặp là rất tốt để thử nghiệm và cải thiện ThreadSafe.
Craig Manson

Danh sách kiểm tra đánh giá mã cho Java Đồng thời tiêu hóa hầu hết các cạm bẫy được đề cập trong các câu trả lời cho câu hỏi này dưới dạng thuận tiện cho việc đánh giá mã hàng ngày.
leventov

Câu trả lời:


125

Vấn đề tương tranh phổ biến nhất mà tôi đã thấy, là không nhận ra rằng một trường được viết bởi một luồng không được đảm bảo để được nhìn thấy bởi một luồng khác. Một ứng dụng phổ biến của điều này:

class MyThread extends Thread {
  private boolean stop = false;

  public void run() {
    while(!stop) {
      doSomeWork();
    }
  }

  public void setStop() {
    this.stop = true;
  }
}

Chừng nào dừng là không dễ bay hơi hoặc setStoprunkhông đồng bộ này không được bảo đảm để làm việc. Sai lầm này đặc biệt tệ hại vì trong 99,999% nó sẽ không thành vấn đề trong thực tế vì chủ đề độc giả cuối cùng sẽ thấy sự thay đổi - nhưng chúng ta không biết anh ấy đã nhìn thấy nó sớm như thế nào.


9
Một giải pháp tuyệt vời cho vấn đề này là biến biến thể hiện dừng thành một nguyên tử. Nó giải quyết tất cả các vấn đề của người không dễ bay hơi, trong khi bảo vệ bạn khỏi các vấn đề JMM.
Kirk Wylie

39
Nó tệ hơn 'trong vài phút' - bạn có thể KHÔNG BAO GIỜ nhìn thấy nó. Trong Mô hình bộ nhớ, JVM được phép tối ưu hóa trong khi (! Dừng) thành while (đúng) và sau đó bạn bị hos. Điều này chỉ có thể xảy ra trên một số máy ảo, chỉ trong chế độ máy chủ, chỉ khi JVM biên dịch lại sau x lần lặp của vòng lặp, v.v ... Ouch!
Cowan

2
Tại sao bạn muốn sử dụng AtomicBoolean trên boolean dễ bay hơi? Tôi đang phát triển cho phiên bản 1.4+, vậy có bất kỳ cạm bẫy nào khi chỉ tuyên bố không ổn định không?
Bể bơi

2
Nick, tôi nghĩ đó là vì CAS nguyên tử thường nhanh hơn cả dễ bay hơi. Nếu bạn đang phát triển cho 1.4, tùy chọn an toàn duy nhất của bạn, IMHO là sử dụng được đồng bộ hóa dưới dạng không ổn định trong 1.4, không có đảm bảo hàng rào bộ nhớ mạnh như trong Java 5.
Kutzi

5
@Thomas: đó là do mô hình bộ nhớ Java. Bạn nên đọc về nó, nếu bạn muốn biết chi tiết về nó (Java Concurrency in Practice của Brian Goetz giải thích nó tốt, vd). Tóm lại: trừ khi bạn sử dụng các từ khóa / cấu trúc đồng bộ hóa bộ nhớ (như dễ bay hơi, được đồng bộ hóa, AtomicXyz, nhưng cả khi một Chủ đề kết thúc), một Chủ đề không đảm bảo bất kỳ thay đổi nào được thực hiện cho bất kỳ trường nào được thực hiện bởi một chủ đề khác
Kutzi

178

Vấn đề đồng thời đau đớn nhất số 1 của tôi từng xảy ra khi hai thư viện nguồn mở khác nhau đã làm một cái gì đó như thế này:

private static final String LOCK = "LOCK";  // use matching strings 
                                            // in two different libraries

public doSomestuff() {
   synchronized(LOCK) {
       this.work();
   }
}

Thoạt nhìn, đây có vẻ là một ví dụ đồng bộ hóa khá nhỏ. Tuy nhiên; bởi vì các chuỗi được tập trung trong Java, chuỗi ký tự "LOCK"hóa ra là cùng một thể hiện của java.lang.String(mặc dù chúng được khai báo hoàn toàn khác biệt với nhau.) Kết quả rõ ràng là xấu.


62
Đây là một trong những lý do tại sao tôi thích Object tĩnh cuối cùng LOCK = new Object ();
Andrzej Doyle

17
Tôi yêu nó - ồ, điều này thật khó chịu :)
Thorbjørn Ravn Andersen

7
Đó là một cái tốt cho Java Puzzlers 2.
Dov Wasserman

12
Trên thực tế ... nó thực sự khiến tôi muốn trình biên dịch từ chối cho phép bạn đồng bộ hóa trên Chuỗi. Cho chuỗi thực tập, không có trường hợp nào là "điều tốt (tm)".
Jared

3
@Jared: "cho đến khi chuỗi được thực hiện" không có ý nghĩa. Chuỗi không kỳ diệu "trở thành" thực tập. String.i INTERN () trả về một đối tượng khác, trừ khi bạn đã có thể hiện chính tắc của Chuỗi được chỉ định. Ngoài ra, tất cả các chuỗi ký tự và các biểu thức hằng có giá trị chuỗi được thực hiện. Luôn luôn. Xem các tài liệu cho String.i INTERN () và §3.10.5 của JLS.
Laurence Gonsalves

65

Một vấn đề kinh điển là thay đổi đối tượng bạn đang đồng bộ hóa trong khi đồng bộ hóa trên nó:

synchronized(foo) {
  foo = ...
}

Các luồng đồng thời khác sau đó được đồng bộ hóa trên một đối tượng khác và khối này không cung cấp loại trừ lẫn nhau mà bạn mong đợi.


19
Có một kiểm tra IDEA cho điều này được gọi là "Đồng bộ hóa trên trường không phải là cuối cùng không có ngữ nghĩa hữu ích". Rất đẹp.
Jen S.

8
Ha ... bây giờ đó là một mô tả bị tra tấn. "Không có ngữ nghĩa hữu ích" tốt hơn có thể được mô tả là "rất có thể bị hỏng". :)
Alex Miller

Tôi nghĩ rằng đó là Java Bitter có cái này trong ReadWriteLock. May mắn thay, bây giờ chúng ta có java.util.concurrency.locks, và Doug là một chút nhiều hơn về quả bóng.
Tom Hawtin - tackline

Tôi cũng đã thấy vấn đề này thường xuyên. Chỉ đồng bộ hóa trên các đối tượng cuối cùng, cho vấn đề đó. FindBugs et al. giúp đỡ
gimpf

Đây chỉ là một vấn đề trong quá trình chuyển nhượng? (xem ví dụ @Alex Miller bên dưới với Bản đồ) Liệu ví dụ bản đồ đó có gặp vấn đề tương tự không?
Alex Beardsley

50

Một vấn đề phổ biến là sử dụng các lớp như Lịch và SimpleDateFormat từ nhiều luồng (thường bằng cách lưu trữ chúng trong một biến tĩnh) mà không đồng bộ hóa. Các lớp này không an toàn cho luồng nên việc truy cập đa luồng cuối cùng sẽ gây ra các vấn đề lạ với trạng thái không nhất quán.


Bạn có biết bất kỳ dự án nguồn mở nào chứa lỗi này trong một số phiên bản của nó không? Tôi đang tìm kiếm các ví dụ cụ thể về lỗi này trong phần mềm thế giới thực.
lập trình lại

47

Kiểm tra khóa kép. Bởi và lớn.

Mô hình mà tôi bắt đầu tìm hiểu các vấn đề khi tôi làm việc tại BEA, là mọi người sẽ kiểm tra một người độc thân theo cách sau:

public Class MySingleton {
  private static MySingleton s_instance;
  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) { s_instance = new MySingleton(); }
    }
    return s_instance;
  }
}

Điều này không bao giờ hoạt động, bởi vì một luồng khác có thể đã được đưa vào khối được đồng bộ hóa và s_instance không còn null. Vì vậy, sự thay đổi tự nhiên là để thực hiện nó:

  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) {
        if(s_instance == null) s_instance = new MySingleton();
      }
    }
    return s_instance;
  }

Điều đó cũng không hoạt động, bởi vì Mô hình bộ nhớ Java không hỗ trợ nó. Bạn cần khai báo s_instance là không ổn định để làm cho nó hoạt động và thậm chí sau đó nó chỉ hoạt động trên Java 5.

Những người không quen thuộc với sự phức tạp của Mô hình bộ nhớ Java mọi lúc mọi nơi .


7
Mẫu enleton singleton giải quyết tất cả những vấn đề này (xem bình luận của Josh Bloch về vấn đề này). Kiến thức về sự tồn tại của nó sẽ được phổ biến rộng rãi hơn trong số các lập trình viên Java.
Robin

Tôi vẫn chưa chạy qua một trường hợp duy nhất trong đó việc khởi tạo lười biếng của một singleton thực sự phù hợp. Và nếu có, chỉ cần khai báo phương thức được đồng bộ hóa.
Dov Wasserman

3
Đây là những gì tôi sử dụng để khởi tạo Lazy các lớp Singleton. Cũng không yêu cầu đồng bộ hóa vì điều này được đảm bảo bởi java ngầm. lớp Foo {Chủ lớp tĩnh {tĩnh Foo foo = new Foo (); } Foo getInstance () {return Holder.foo; }}
Irfan Zulfiqar

Irfan, đó được gọi là phương pháp của Pugh, từ những gì tôi nhớ lại
Chris R

@Robin, không đơn giản hơn khi chỉ sử dụng trình khởi tạo tĩnh? Những người luôn được đảm bảo để chạy đồng bộ.
matt b

47

Không đồng bộ hóa đúng cách trên các đối tượng được trả về Collections.synchronizedXXX(), đặc biệt là trong quá trình lặp hoặc nhiều thao tác:

Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());

...

if(!map.containsKey("foo"))
    map.put("foo", "bar");

Điều đó sai . Mặc dù có các hoạt động đơn lẻ synchronized, trạng thái bản đồ giữa việc gọi containsputcó thể được thay đổi bởi một luồng khác. Nó nên là:

synchronized(map) {
    if(!map.containsKey("foo"))
        map.put("foo", "bar");
}

Hoặc với ConcurrentMapviệc thực hiện:

map.putIfAbsent("foo", "bar");

5
Hoặc tốt hơn, sử dụng một concảnHashMap và put IfAbsent.
Tom Hawtin - tackline

37

Mặc dù có thể không chính xác những gì bạn đang yêu cầu, nhưng vấn đề liên quan đến đồng thời thường gặp nhất mà tôi gặp phải (có lẽ là do nó xuất hiện trong mã đơn luồng thông thường) là một

java.util.ConcurrentModificationException

gây ra bởi những thứ như:

List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }

Không, đó hoàn toàn là những gì tôi đang tìm kiếm. Cảm ơn!
Alex Miller

30

Có thể dễ dàng nghĩ rằng các bộ sưu tập được đồng bộ hóa mang lại cho bạn sự bảo vệ nhiều hơn so với thực tế và quên giữ khóa giữa các cuộc gọi. Tôi đã thấy lỗi này một vài lần:

 List<String> l = Collections.synchronizedList(new ArrayList<String>());
 String[] s = l.toArray(new String[l.size()]);

Ví dụ, trong dòng thứ hai ở trên, toArray()size()các phương pháp đều đề an toàn trong quyền riêng của họ, nhưng size()được đánh giá tách biệt khỏi toArray(), và các khóa trên danh sách không được tổ chức giữa hai cuộc gọi này.

Nếu bạn chạy mã này với một luồng khác đồng thời loại bỏ các mục khỏi danh sách, sớm hay muộn bạn sẽ kết thúc với một String[]trả về mới lớn hơn yêu cầu để giữ tất cả các thành phần trong danh sách và có giá trị null ở đuôi. Thật dễ dàng để nghĩ rằng bởi vì hai phương thức gọi đến Danh sách xảy ra trong một dòng mã duy nhất, đây là một hoạt động nguyên tử, nhưng thực tế không phải vậy.


5
ví dụ tốt. Tôi nghĩ rằng tôi muốn nói điều này nhiều hơn vì "thành phần của các hoạt động nguyên tử không phải là nguyên tử". (Xem trường biến động ++ để biết ví dụ đơn giản khác)
Alex Miller

29

Lỗi phổ biến nhất mà chúng tôi thấy nơi tôi làm việc là các lập trình viên thực hiện các hoạt động dài, như các cuộc gọi máy chủ, trên EDT, khóa GUI trong vài giây và khiến ứng dụng không phản hồi.


một trong những câu trả lời tôi ước tôi có thể đưa ra nhiều hơn một điểm cho
Epaga

2
EDT = Chủ đề công văn sự kiện
mjj1409

28

Quên chờ đợi () (hoặc condition.await ()) trong một vòng lặp, kiểm tra xem điều kiện chờ có thực sự đúng không. Nếu không có điều này, bạn sẽ gặp phải các lỗi từ đánh thức giả (). Sử dụng Canonical nên là:

 synchronized (obj) {
     while (<condition does not hold>) {
         obj.wait();
     }
     // do stuff based on condition being true
 }

26

Một lỗi phổ biến khác là xử lý ngoại lệ kém. Khi một luồng nền ném một ngoại lệ, nếu bạn không xử lý nó đúng cách, bạn có thể không thấy dấu vết ngăn xếp nào cả. Hoặc có lẽ tác vụ nền của bạn ngừng chạy và không bao giờ bắt đầu lại vì bạn không thể xử lý ngoại lệ.


Đúng, và có những công cụ tốt để xử lý việc này bây giờ với trình xử lý.
Alex Miller

3
Bạn có thể gửi liên kết đến bất kỳ bài viết hoặc tài liệu tham khảo giải thích điều này chi tiết hơn?
Abhijeet Kashnia

22

Cho đến khi tôi mất một lớp học với Brian Goetz Tôi đã không nhận ra rằng không đồng bộ gettercủa một lĩnh vực tư nhân bị biến đổi thông qua một đồng bộ setterkhông bao giờ được đảm bảo để trả về giá trị được cập nhật. Chỉ khi một biến được bảo vệ bởi khối được đồng bộ hóa trên cả hai lần đọc VÀ ghi, bạn mới có được sự đảm bảo về giá trị mới nhất của biến.

public class SomeClass{
    private Integer thing = 1;

    public synchronized void setThing(Integer thing)
        this.thing = thing;
    }

    /**
     * This may return 1 forever and ever no matter what is set
     * because the read is not synched
     */
    public Integer getThing(){
        return thing;  
    }
}

5
Trong các JVM sau này (1,5 và chuyển tiếp, tôi nghĩ vậy), việc sử dụng biến động cũng sẽ khắc phục điều đó.
James Schek

2
Không cần thiết. dễ bay hơi cung cấp cho bạn giá trị mới nhất để ngăn chặn trả về 1 mãi mãi, nhưng nó không cung cấp khóa. Nó gần, nhưng không hoàn toàn giống nhau.
John Russell

1
@JohnRussell Tôi nghĩ rằng không ổn định đảm bảo một mối quan hệ xảy ra trước khi. đó không phải là "khóa" sao? "Một ghi vào một biến dễ bay hơi (§8.3.1.4) v được đồng bộ hóa - với tất cả các lần đọc tiếp theo của v bởi bất kỳ luồng nào (trong đó tiếp theo được xác định theo thứ tự đồng bộ hóa)."
Shawn

15

Nghĩ rằng bạn đang viết mã đơn luồng, nhưng sử dụng số liệu thống kê có thể thay đổi (bao gồm cả singletons). Rõ ràng chúng sẽ được chia sẻ giữa các chủ đề. Điều này xảy ra đáng ngạc nhiên thường xuyên.


3
Vâng, thực sự! Thống kê đột biến phá vỡ chủ đề giam cầm. Đáng ngạc nhiên, tôi chưa bao giờ tìm thấy bất cứ điều gì về cạm bẫy này trong cả JCiP hoặc CPJ.
Julien Chastang

Tôi hy vọng điều này là hiển nhiên đối với những người làm chương trình đồng thời. Nhà nước toàn cầu nên là nơi đầu tiên để kiểm tra an toàn luồng.
gtrak

1
@Gary Thing là, họ không nghĩ rằng họ đang lập trình đồng thời.
Tom Hawtin - tackline

15

Các cuộc gọi phương thức tùy ý không nên được thực hiện từ bên trong các khối được đồng bộ hóa.

Dave Ray đã chạm vào điều này trong câu trả lời đầu tiên của anh ấy, và trên thực tế tôi cũng gặp phải một sự bế tắc cũng phải làm với việc gọi các phương thức trên người nghe từ bên trong một phương thức được đồng bộ hóa. Tôi nghĩ rằng bài học tổng quát hơn là các cuộc gọi phương thức không nên được thực hiện "trong tự nhiên" từ trong một khối được đồng bộ hóa - bạn không biết liệu cuộc gọi sẽ kéo dài, dẫn đến bế tắc hoặc bất cứ điều gì.

Trong trường hợp này và thông thường nói chung, giải pháp là giảm phạm vi của khối được đồng bộ hóa để chỉ bảo vệ một phần riêng tư quan trọng của mã.

Ngoài ra, vì chúng tôi hiện đang truy cập Bộ sưu tập người nghe bên ngoài một khối được đồng bộ hóa, chúng tôi đã thay đổi nó thành Bộ sưu tập sao chép trên ghi. Hoặc chúng ta có thể chỉ cần tạo một bản sao phòng thủ của Bộ sưu tập. Vấn đề là, thường có những lựa chọn thay thế để truy cập an toàn vào Bộ sưu tập các đối tượng không xác định.


13

Lỗi liên quan đến đồng thời gần đây nhất mà tôi gặp phải là một đối tượng mà trong hàm tạo của nó đã tạo ra một ExecutorService, nhưng khi đối tượng không còn được tham chiếu nữa, nó chưa bao giờ tắt ExecutorService. Do đó, trong một khoảng thời gian vài tuần, hàng ngàn luồng bị rò rỉ, cuối cùng khiến hệ thống gặp sự cố. (Về mặt kỹ thuật, nó không gặp sự cố, nhưng nó đã ngừng hoạt động bình thường, trong khi vẫn tiếp tục chạy.)

Về mặt kỹ thuật, tôi cho rằng đây không phải là vấn đề tương tranh, nhưng đó là vấn đề liên quan đến việc sử dụng các thư viện java.util.concurrency.


11

Đồng bộ hóa không cân bằng, đặc biệt là đối với Bản đồ dường như là một vấn đề khá phổ biến. Nhiều người tin rằng việc đồng bộ hóa trên các bản đồ (không phải là Bản đồ đồng thời, nhưng nói HashMap) và không đồng bộ hóa trên get là đủ. Tuy nhiên, điều này có thể dẫn đến một vòng lặp vô hạn trong quá trình băm lại.

Tuy nhiên, cùng một vấn đề (đồng bộ hóa một phần) có thể xảy ra ở bất cứ nơi nào bạn có trạng thái chia sẻ với đọc và ghi.


11

Tôi đã gặp một vấn đề tương tranh với Servlets, khi có các trường có thể thay đổi sẽ được giải quyết theo từng yêu cầu. Nhưng chỉ có một ví dụ của servlet cho tất cả các yêu cầu, vì vậy điều này hoạt động hoàn hảo trong một môi trường người dùng duy nhất nhưng khi có nhiều hơn một người dùng yêu cầu các dịch vụ không thể đoán được kết quả.

public class MyServlet implements Servlet{
    private Object something;

    public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException{
        this.something = request.getAttribute("something");
        doSomething();
    }

    private void doSomething(){
        this.something ...
    }
}

10

Không chính xác là một lỗi, nhưng tội lỗi tồi tệ nhất là cung cấp một thư viện mà bạn định người khác sử dụng, nhưng không nêu rõ các lớp / phương thức nào là an toàn cho luồng và những cái nào chỉ phải được gọi từ một luồng, v.v.

Nhiều người nên sử dụng các chú thích đồng thời (ví dụ @ThreadSafe, @GuardedBy, v.v.) được mô tả trong cuốn sách của Goetz.


9

Vấn đề lớn nhất của tôi luôn là sự bế tắc, đặc biệt là do người nghe bị sa thải với một khóa được giữ. Trong những trường hợp này, thật dễ dàng để có được khóa ngược giữa hai luồng. Trong trường hợp của tôi, giữa một mô phỏng đang chạy trong một luồng và trực quan hóa mô phỏng đang chạy trong luồng UI.

EDIT: Đã chuyển phần thứ hai để tách câu trả lời.


Bạn có thể chia câu cuối cùng ra thành một câu trả lời riêng không? Hãy giữ nó 1 mỗi bài. Đây là hai cái thực sự tốt.
Alex Miller

9

Bắt đầu một luồng trong hàm tạo của một lớp là có vấn đề. Nếu lớp được mở rộng, luồng có thể được bắt đầu trước khi hàm tạo của lớp con được thực thi.


8

Các lớp có thể thay đổi trong cấu trúc dữ liệu được chia sẻ

Thread1:
    Person p = new Person("John");
    sharedMap.put("Key", p);
    assert(p.getName().equals("John");  // sometimes passes, sometimes fails

Thread2:
    Person p = sharedMap.get("Key");
    p.setName("Alfonso");

Khi điều này xảy ra, mã phức tạp hơn nhiều mà ví dụ đơn giản hóa này. Sao chép, tìm và sửa lỗi là khó. Có lẽ có thể tránh được nếu chúng ta có thể đánh dấu các lớp nhất định là các cấu trúc dữ liệu bất biến và nhất định là chỉ giữ các đối tượng bất biến.


8

Đồng bộ hóa trên một chuỗi bằng chữ hoặc hằng được xác định bởi một chuỗi ký tự là (có khả năng) là một vấn đề vì chuỗi ký tự được thực hiện và sẽ được chia sẻ bởi bất kỳ ai khác trong JVM bằng cách sử dụng cùng một chuỗi ký tự. Tôi biết vấn đề này đã xuất hiện trong các máy chủ ứng dụng và các tình huống "container" khác.

Thí dụ:

private static final String SOMETHING = "foo";

synchronized(SOMETHING) {
   //
}

Trong trường hợp này, bất kỳ ai sử dụng chuỗi "foo" để khóa đều chia sẻ cùng một khóa.


Có khả năng nó bị khóa. Vấn đề là ngữ nghĩa trên WHEN String được thực hiện là không xác định (hoặc, IMNSHO, không xác định). Hằng số thời gian biên dịch của "foo" được thực hiện, "foo" đến từ giao diện mạng chỉ được thực hiện nếu bạn làm như vậy.
Kirk Wylie

Phải, đó là lý do tại sao tôi đặc biệt sử dụng hằng chuỗi ký tự, được đảm bảo để được thực tập.
Alex Miller

8

Tôi tin rằng trong tương lai, vấn đề chính với Java sẽ là (thiếu) đảm bảo khả năng hiển thị cho các nhà xây dựng. Ví dụ: nếu bạn tạo lớp sau

class MyClass {
    public int a = 1;
}

và sau đó chỉ cần đọc thuộc tính của MyClass a từ một luồng khác, MyClass.a có thể là 0 hoặc 1, tùy thuộc vào tâm trạng và cách triển khai của JavaVM. Ngày nay, cơ hội cho 'a' là 1 là rất cao. Nhưng trên các máy NUMA trong tương lai, điều này có thể khác. Nhiều người không nhận thức được điều này và tin rằng họ không cần quan tâm đến đa luồng trong giai đoạn khởi tạo.


Tôi thấy điều này hơi đáng ngạc nhiên, nhưng tôi biết bạn là một anh chàng thông minh Tim nên tôi sẽ lấy nó tham khảo. :) Tuy nhiên, nếu một trận chung kết, điều này sẽ không phải là một mối quan tâm, đúng không? Sau đó, bạn sẽ bị ràng buộc bởi ngữ nghĩa đóng băng cuối cùng trong quá trình xây dựng?
Alex Miller

Tôi vẫn tìm thấy những thứ trong JMM làm tôi ngạc nhiên, vì vậy tôi sẽ không tin tôi, nhưng tôi khá chắc chắn về điều này. Xem thêm cs.umd.edu/~pugh/java/memoryModel/ . Nếu trường là cuối cùng thì nó sẽ không thành vấn đề, sau đó nó sẽ hiển thị sau giai đoạn khởi tạo.
Tim Jansen

2
Đây chỉ là một vấn đề, nếu tham chiếu của phiên bản mới được tạo đã được sử dụng trước khi hàm tạo trở lại / kết thúc. Ví dụ, lớp đăng ký chính nó trong khi xây dựng trong một nhóm chung và các luồng khác bắt đầu truy cập nó.
ReneS

3
MyClass.a biểu thị quyền truy cập tĩnh và 'a' không phải là thành viên tĩnh của MyClass. Ngoài ra, đó là trạng thái 'ReneS', đây chỉ là vấn đề nếu tham chiếu đến đối tượng chưa hoàn thành bị rò rỉ, chẳng hạn như thêm 'this' vào một số bản đồ bên ngoài trong hàm tạo.
Markus Jevring

7

Sai lầm ngớ ngẩn nhất mà tôi thường mắc phải là quên đồng bộ hóa trước khi gọi thông báo () hoặc chờ () trên một đối tượng.


8
Không giống như hầu hết các vấn đề tương tranh, điều này có dễ tìm không? Ít nhất bạn cũng có được một IllegalMonitorStateException tại đây ...
Lập trình viên ngoài vòng pháp luật

Rất may, nó rất dễ tìm thấy ... nhưng nó vẫn là một sai lầm ngớ ngẩn làm lãng phí thời gian của tôi nhiều hơn bình thường :)
Dave Ray

7

Sử dụng một "đối tượng mới ()" cục bộ làm mutex.

synchronized (new Object())
{
    System.out.println("sdfs");
}

Điều này là vô ích.


2
Điều này có thể là vô ích, nhưng hành động đồng bộ hóa hoàn toàn làm một số điều thú vị ... Chắc chắn việc tạo ra một Đối tượng mới mỗi lần là một sự lãng phí hoàn toàn.
TREE

4
Nó không vô dụng. Đó là rào cản bộ nhớ mà không có khóa.
David Roussel

1
@David: vấn đề duy nhất - jvm có thể tối ưu hóa nó bằng cách loại bỏ khóa như vậy
yetanothercoder

@insighter Tôi thấy ý kiến của bạn được chia sẻ ibm.com/developerworks/java/library/j-jtp10185/index.html Tôi đồng ý rằng đó là một điều ngớ ngẩn để làm, vì bạn không biết khi nào hàng rào memoery của bạn sẽ đồng bộ hóa, tôi chỉ là chỉ ra rằng đang làm nhiều hơn mà không có gì.
David Roussel

7

Một vấn đề 'đồng thời' phổ biến khác là sử dụng mã được đồng bộ hóa khi hoàn toàn không cần thiết. Ví dụ tôi vẫn thấy các lập trình viên sử dụng StringBufferhoặc thậm chí java.util.Vector(như các biến cục bộ của phương thức).


1
Đây không phải là một vấn đề, nhưng không cần thiết, vì nó cho JVM đồng bộ hóa dữ liệu với bộ nhớ chung và do đó có thể chạy không tốt trên nhiều cpus, do đó, không ai sử dụng khối đồng bộ hóa theo kiểu đồng thời.
ReneS

6

Nhiều đối tượng được bảo vệ khóa nhưng thường được truy cập liên tiếp. Chúng tôi đã gặp một vài trường hợp trong đó các khóa được lấy bằng các mã khác nhau theo các thứ tự khác nhau, dẫn đến bế tắc.


5

Không nhận ra rằng thistrong một lớp bên trong không phải là thiscủa lớp bên ngoài. Thông thường trong một lớp bên trong vô danh mà thực hiện Runnable. Vấn đề gốc là bởi vì đồng bộ hóa là một phần của tất cả các Objects nên không có kiểm tra kiểu tĩnh. Tôi đã thấy điều này ít nhất hai lần trên usenet và nó cũng xuất hiện trong Brian Goetz'z Java đồng thời trong thực tiễn.

Việc đóng cửa BGGA không phải chịu đựng điều này vì không có thissự đóng cửa ( thistham chiếu lớp bên ngoài). Nếu bạn sử dụng các thisđối tượng không phải là khóa thì nó sẽ giải quyết vấn đề này và các vấn đề khác.


3

Sử dụng một đối tượng toàn cầu như một biến tĩnh để khóa.

Điều này dẫn đến hiệu suất rất xấu vì ganh đua.


Vâng, đôi khi, đôi khi không. Nếu mọi chuyện dễ dàng đến thế ...
gimpf

Giả sử rằng luồng giúp hoàn toàn tăng hiệu năng cho sự cố đã cho, nó luôn làm giảm hiệu suất ngay khi có nhiều hơn một luồng truy cập mã được bảo vệ bởi khóa.
su hào

3

Honesly? Trước khi xuất hiện java.util.concurrent, vấn đề phổ biến nhất mà tôi thường gặp phải là cái mà tôi gọi là "luồng-đập": Các ứng dụng sử dụng các luồng để xử lý đồng thời, nhưng sinh ra quá nhiều trong số chúng và cuối cùng bị đập.


Bạn đang ám chỉ rằng bạn gặp phải nhiều vấn đề hơn khi java.util.conc hiện có sẵn?
Andrzej Doyle
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.