2013-05-20 22 views
6

Tôi biết sự khác biệt giữa StringBuffer và StringBuilder. read here!Hãy tưởng tượng một tình huống đồng thời thực sự trong đó StringBuffer nên được sử dụng hơn StringBuilder?

Và nói chung, như javadoc nói,

Nếu có thể, nó được khuyến khích mà lớp này được sử dụng trong ưu đãi để StringBuffer như nó sẽ nhanh hơn dưới hầu hết các trường.

Tuy nhiên, javadoc của StringBuilder cũng nói:

Instances của StringBuilder không an toàn để sử dụng bởi nhiều chủ đề. Nếu yêu cầu đồng bộ hóa như vậy thì bạn nên sử dụng {@link java.lang.StringBuffer}

Vì vậy, tôi tự hỏi, có phải trường hợp StringBuffer được ưa thích thực sự tồn tại không? Và khi chuỗi có thể thay đổi được sử dụng chủ yếu trong một chuỗi đơn lẻ, bất kỳ ai có thể cho tôi một kịch bản thực tế đồng thời mà StringBuffer được ưa thích không?

+2

Theo Joshua Bloch, "thread safe" không phải là một hiện tượng hoàn toàn có/không có. Ông liệt kê 4 loại an toàn chủ đề riêng biệt được phân biệt chủ yếu bởi mức độ mà khách hàng của API phải chủ động quản lý hoặc đồng bộ hóa. Điều này được phát triển bởi Peter Lawrey trong phản ứng của ông là tốt. Không có lớp nào là một hòn đảo. – scottb

Trả lời

5

Lý do StringBuffer là chủ đề an toàn là trở lại trong ngày khi phiên bản đầu tiên của java api được thiết kế, mọi người tiếp cận đồng thời khác với ngày nay. Ý kiến ​​phổ biến là các đối tượng nên là luồng an toàn - bởi vì Java hỗ trợ các luồng và mọi người có thể sử dụng bất kỳ lớp JDK nào trong nhiều luồng. Sau đó, khi Java bắt đầu được tối ưu hóa cho thời gian thực thi, chi phí của các khối đồng bộ không cần thiết này bắt đầu trở thành một vấn đề, vì vậy các API mới hơn được thiết kế để không được đồng bộ hóa. Tuy nhiên sau đó, JVM bắt đầu tối ưu hóa ổ khóa đến mức các ổ khóa không bị cản trở trở nên cơ bản miễn phí, khiến cho toàn bộ quyết định là điểm moot.

ChuỗiBuffer vẫn an toàn theo luồng, vì mã cũ có thể dựa vào đó là an toàn chỉ. Đó là xa sử dụng điển hình, nhưng có thể hiểu được. Ví dụ:

Ví dụ: giả sử bạn đang viết một ứng dụng ghi nhật ký để chuyển tiếp các mục nhập nhật ký tới máy chủ trung tâm. Vì chúng tôi không muốn chặn người gọi trong khi chờ I/O mạng, chúng tôi làm điều đó trong một chuỗi chuyên dụng. Các chủ đề khác sẽ tích lũy các mục nhật ký của chúng trong một StringBuffer:

class RemoteLogger implements Runnable, Appender { 
    final StringBuffer buffer = new StringBuffer(); 

    void append(String s) { 
     buffer.append(s); 
    } 

    public void run() { 
     for (;;) { 
      Thread.sleep(100); 

      String message = buffer.toString(); 
      sendToServer(message); 
      buffer.delete(0, message.length()); 
     } 
    } 
} 
3

Thực sự ở bất kỳ nơi nào khi bộ đệm văn bản có thể được truy cập đồng thời.

Ví dụ: cách có nhiều luồng ghi sẽ xuất dữ liệu trên mạng. Trong trường hợp đó, có lẽ họ chia sẻ một bộ đệm văn bản phổ biến và chỉ ghi trực tiếp vào nó và khi bộ đệm đầy, nó có thể được gửi qua mạng.

+0

Đề xuất hay, nhưng tôi cá là bạn không thể viết một cách an toàn mà không phải sử dụng đồng bộ. Tôi đã có thể sử dụng một BufferedOutputStream bản thân mình. ;) –

+1

May mắn cho tôi anh ấy không muốn thực hiện để đi cùng với trường hợp sử dụng;) – greedybuddha

+0

Suy nghĩ về một cách để viết này mà không đồng bộ hóa, bây giờ tôi tin rằng tôi có thể làm điều đó (cảm ơn cho sáng nay một tư tưởng tập thể dục), miễn là bạn cho tôi khả năng biết cuối cùng rằng một bộ đệm sẽ không được ghi vào nữa. Vì vậy, ý tưởng chỉ là sử dụng một getter để có được bộ đệm chung có thể được chia sẻ và ghi vào. Nếu getter thấy chiều dài của bộ đệm lớn hơn một số k, nó đặt bộ đệm vào hàng đợi được viết và trả về một bộ đệm mới. giả sử rằng tại một số điểm bạn biết bộ đệm đầu tiên trên hàng đợi không được sử dụng, bạn là vàng – greedybuddha

4

Câu trả lời đơn giản là KHÔNG. IMHO không có tình huống sane nơi bạn sẽ sử dụng StringBuffer thay vì StringBuilder hoặc một lớp khác. Làm cho chuỗi StringBuffer an toàn có thể làm cho mã của bạn ít thread an toàn hơn vì mọi người nhầm lẫn cho rằng nếu bạn đã sử dụng StringBuffer bạn mã là luồng an toàn khi đây không phải là trường hợp.

Nếu bạn đã sử dụng StringBuffer, tại một thời điểm nào đó bạn phải sử dụng đồng bộ, mặc dù không phải lúc nào cũng rõ ràng với hầu hết các nhà phát triển, và tôi đã thấy nhiều lỗi ở đây (ngay cả trong thư viện trưởng thành). t được thực hiện một cách chính xác. Nó là tốt hơn bạn sử dụng StringBuilder và làm khóa enternally, nhất quán.

Why a synchronized StringBuffer was never a good idea.

là trường hợp đó StringBuffer được ưa thích thực sự tồn tại

Có một trường hợp sử dụng; bạn có một thư viện chỉ chấp nhận StringBuffer trong API. Đây là thiết kế kém vì lý do nêu trên, nhưng không phải thư viện là hoàn hảo. ;)

+0

Thật không may, bản thân Java có ít nhất một lớp chỉ chấp nhận StringBuffer (không phải StringBuilder), ví dụ: [Matcher.appendReplacement (StringBuffer, String)] (http://docs.oracle.com/javase/6/docs/api/java/util/regex/Matcher.html). –

+0

Trong khi tôi đồng ý với tình cảm chung, tôi nghĩ rằng bạn đang có một chút quá tuyệt đối trong việc nói rằng đồng bộ hóa bên ngoài là * luôn luôn * yêu cầu. Ít nhất, tôi không thấy làm thế nào mã tôi đăng trong câu trả lời của tôi yêu cầu đồng bộ hóa bổ sung? – meriton

+0

@meriton Tham gia cho tất cả các chủ đề có hiệu quả đồng bộ hóa tất cả các chủ đề để ngăn chặn. Bạn có thể đưa ra một ví dụ trong đó bạn gắn thêm nhiều thứ vào một luồng theo một cách lành mạnh vì điều này không hiển nhiên đối với nhiều nhà phát triển? –

2

Chương trình sau đây đôi khi sẽ ném ngoại lệ khi sử dụng StringBuilder, nhưng sẽ không bao giờ ném ngoại lệ khi sử dụng StringBuffer.

Chương trình:

public class StringBuilderConcurrent { 
    static final StringBuilder sb = new StringBuilder(); // shared memory 

    public static void main(String[] args) throws Exception { 
     int NUM_WRITERS = 300; 
     ArrayList<WriterThread> threads = new ArrayList<WriterThread>(NUM_WRITERS); 
     for (int i = 0; i < NUM_WRITERS; i++) { 
      WriterThread wt = new WriterThread("writerThread" + i); 
      threads.add(wt); 
      wt.start(); 
     } 
     for (int i = 0; i < threads.size(); i++) { 
      threads.get(i).join(); 
     }  
     System.out.println(sb); 
    } 

    public static class WriterThread extends Thread { 
     public WriterThread(String name) { 
      super(name); 
     } 
     public void run() { 
      String nameNl = this.getName() + "\n"; 
      for (int i = 1; i < 20; i++) { 
       sb.append(nameNl); 
      } 
     } 
    }; 
} 

Bởi vì StringBuilder (sb) không phải là thread an toàn, có nhiều đề ghi dữ liệu vào sb có thể dẫn đến sb trở thành bị hỏng (ví dụ ký tự null bất ngờ, chữ của một từ xen kẽ với một số từ của người khác bức thư).Nó cũng có thể cho trạng thái nội sb 's để trở thành đủ không phù hợp mà một ngoại lệ có thể được ném:

Exception in thread "writerThread0" java.lang.ArrayIndexOutOfBoundsException 
    at java.lang.System.arraycopy(Native Method) 
    at java.lang.String.getChars(String.java:854) 
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:391) 
    at java.lang.StringBuilder.append(StringBuilder.java:119) 
    at test.StringBuilderConcurrent$WriterThread.run(StringBuilderConcurrent.java:35) 

Các chương trình sau đây là giống hệt nhau để là người đầu tiên ngoại trừ nó sử dụng StringBuffer thay vì StringBuilder. Nó sẽ không bao giờ gặp phải một ArrayIndexOutOfBoundsException.

public class StringBufferConcurrent { 
    static final StringBuffer sb = new StringBuffer(); // shared memory 

    public static void main(String[] args) throws Exception { 
     int NUM_WRITERS = 300; 
     ArrayList<WriterThread> threads = new ArrayList<WriterThread>(NUM_WRITERS); 
     for (int i = 0; i < NUM_WRITERS; i++) { 
      WriterThread wt = new WriterThread("writerThread" + i); 
      threads.add(wt); 
      wt.start(); 
     } 
     for (int i = 0; i < threads.size(); i++) { 
      threads.get(i).join(); 
     } 

     System.out.println(sb); 
    } 

    public static class WriterThread extends Thread { 
     public WriterThread(String name) { 
      super(name); 
     } 
     public void run() { 
      String nameNl = this.getName() + "\n"; 
      for (int i = 1; i < 20; i++) { 
       sb.append(nameNl); 
      } 
     } 
    }; 
} 

Cho dù các chương trình này đại diện cho một vấn đề "thực tế" là một câu hỏi khá chủ quan. Tôi sẽ để lại sự phán xét đó cho khán giả.

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