<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## Chapter 11. Concurrency(并發) ### Item 79: Avoid excessive synchronization(避免過度同步) Item 78 warns of the dangers of insufficient synchronization. This item concerns the opposite problem. Depending on the situation, excessive synchronization can cause reduced performance, deadlock, or even nondeterministic behavior. [Item-78](/Chapter-11/Chapter-11-Item-78-Synchronize-access-to-shared-mutable-data.md) 警告我們同步不到位的危險。本條目涉及相反的問題。根據不同的情況,過度的同步可能導致性能下降、死鎖甚至不確定行為。 **To avoid liveness and safety failures, never cede control to the client within a synchronized method or block.** In other words, inside a synchronized region, do not invoke a method that is designed to be overridden, or one provided by a client in the form of a function object (Item 24). From the perspective of the class with the synchronized region, such methods are alien. The class has no knowledge of what the method does and has no control over it. Depending on what an alien method does, calling it from a synchronized region can cause exceptions, deadlocks, or data corruption. **為避免活性失敗和安全故障,永遠不要在同步方法或塊中將控制權交給客戶端。** 換句話說,在同步區域內,不要調用一個設計為被覆蓋的方法,或者一個由客戶端以函數對象的形式提供的方法([Item-24](/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md))。從具有同步區域的類的角度來看,這種方法是不一樣的。類不知道該方法做什么,也無法控制它。Depending on what an alien method does,從同步區域調用它可能會導致異常、死鎖或數據損壞。 To make this concrete, consider the following class, which implements an observable set wrapper. It allows clients to subscribe to notifications when elements are added to the set. This is the Observer pattern [Gamma95]. For brevity’s sake, the class does not provide notifications when elements are removed from the set, but it would be a simple matter to provide them. This class is implemented atop the reusable ForwardingSet from Item 18 (page 90): 要使這個問題具體化,請考慮下面的類,它實現了一個可視 Set 包裝器。當元素被添加到集合中時,它允許客戶端訂閱通知。這是觀察者模式 [Gamma95]。為了簡單起見,當元素從集合中刪除時,該類不提供通知,即使要提供通知也很簡單。這個類是在 [Item-18](/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md)(第 90 頁)的可復用 ForwardingSet 上實現的: ``` // Broken - invokes alien method from synchronized block! public class ObservableSet<E> extends ForwardingSet<E> { public ObservableSet(Set<E> set) { super(set); } private final List<SetObserver<E>> observers= new ArrayList<>(); public void addObserver(SetObserver<E> observer) { synchronized(observers) { observers.add(observer); } } public boolean removeObserver(SetObserver<E> observer) { synchronized(observers) { return observers.remove(observer); } } private void notifyElementAdded(E element) { synchronized(observers) { for (SetObserver<E> observer : observers) observer.added(this, element); } } @Override public boolean add(E element) { boolean added = super.add(element); if (added) notifyElementAdded(element); return added; } @Override public boolean addAll(Collection<? extends E> c) { boolean result = false; for (E element : c) result |= add(element); // Calls notifyElementAdded return result; } } ``` Observers subscribe to notifications by invoking the addObserver method and unsubscribe by invoking the removeObserver method. In both cases, an instance of this callback interface is passed to the method. 觀察者通過調用 addObserver 方法訂閱通知,通過調用 removeObserver 方法取消訂閱。在這兩種情況下,都會將此回調接口的實例傳遞給方法。 ``` @FunctionalInterface public interface SetObserver<E> { // Invoked when an element is added to the observable set void added(ObservableSet<E> set, E element); } ``` This interface is structurally identical to `BiConsumer<ObservableSet<E>,E>`. We chose to define a custom functional interface because the interface and method names make the code more readable and because the interface could evolve to incorporate multiple callbacks. That said, a reasonable argument could also be made for using BiConsumer (Item 44). 這個接口在結構上與 `BiConsumer<ObservableSet<E>,E>` 相同。我們選擇定義一個自定義函數式接口,因為接口和方法名稱使代碼更具可讀性,而且接口可以演化為包含多個回調。也就是說,使用 BiConsumer 也是合理的([Item-44](/Chapter-7/Chapter-7-Item-44-Favor-the-use-of-standard-functional-interfaces.md))。 On cursory inspection, ObservableSet appears to work fine. For example, the following program prints the numbers from 0 through 99: 粗略地檢查一下,ObservableSet 似乎工作得很好。例如,下面的程序打印從 0 到 99 的數字: ``` public static void main(String[] args) { ObservableSet<Integer> set =new ObservableSet<>(new HashSet<>()); set.addObserver((s, e) -> System.out.println(e)); for (int i = 0; i < 100; i++) set.add(i); } ``` Now let’s try something a bit fancier. Suppose we replace the addObserver call with one that passes an observer that prints the Integer value that was added to the set and removes itself if the value is 23: 現在讓我們嘗試一些更奇特的東西。假設我們將 addObserver 調用替換為一個傳遞觀察者的調用,該觀察者打印添加到集合中的整數值,如果該值為 23,則該調用將刪除自身: ``` set.addObserver(new SetObserver<>() { public void added(ObservableSet<Integer> s, Integer e) { System.out.println(e); if (e == 23) s.removeObserver(this); } }); ``` Note that this call uses an anonymous class instance in place of the lambda used in the previous call. That is because the function object needs to pass itself to s.removeObserver, and lambdas cannot access themselves (Item 42). 注意,這個調用使用一個匿名類實例來代替前面調用中使用的 lambda 表達式。這是因為函數對象需要將自己傳遞給 `s.removeObserver`,而 lambda 表達式不能訪問自身([Item-42](/Chapter-7/Chapter-7-Item-42-Prefer-lambdas-to-anonymous-classes.md))。 You might expect the program to print the numbers 0 through 23, after which the observer would unsubscribe and the program would terminate silently. In fact, it prints these numbers and then throws a ConcurrentModificationException. The problem is that notifyElementAdded is in the process of iterating over the observers list when it invokes the observer’s added method. The added method calls the observable set’s removeObserver method, which in turn calls the method observers.remove. Now we’re in trouble. We are trying to remove an element from a list in the midst of iterating over it, which is illegal. The iteration in the notifyElementAdded method is in a synchronized block to prevent concurrent modification, but it doesn’t prevent the iterating thread itself from calling back into the observable set and modifying its observers list. 你可能希望程序打印數字 0 到 23,然后觀察者將取消訂閱,程序將無聲地終止。實際上,它打印這些數字,然后拋出 ConcurrentModificationException。問題在于 notifyElementAdded 在調用觀察者的 added 方法時,正在遍歷 observers 列表。added 方法調用可觀察集的 removeObserver 方法,該方法反過來調用方法 `observers.remove`。現在我們有麻煩了。我們試圖在遍歷列表的過程中從列表中刪除一個元素,這是非法的。notifyElementAdded 方法中的迭代位于一個同步塊中,以防止并發修改,但是無法防止迭代線程本身回調到可觀察的集合中,也無法防止修改它的 observers 列表。 Now let’s try something odd: let’s write an observer that tries to unsubscribe, but instead of calling removeObserver directly, it engages the services of another thread to do the deed. This observer uses an executor service (Item 80): 現在讓我們嘗試一些奇怪的事情:讓我們編寫一個觀察者來嘗試取消訂閱,但是它沒有直接調用 removeObserver,而是使用另一個線程的服務來執行這個操作。該觀察者使用 executor 服務([Item-80](/Chapter-11/Chapter-11-Item-80-Prefer-executors,-tasks,-and-streams-to-threads.md)): ``` // Observer that uses a background thread needlessly set.addObserver(new SetObserver<>() { public void added(ObservableSet<Integer> s, Integer e) { System.out.println(e); if (e == 23) { ExecutorService exec = Executors.newSingleThreadExecutor(); try { exec.submit(() -> s.removeObserver(this)).get(); } catch (ExecutionException | InterruptedException ex) { throw new AssertionError(ex); } finally { exec.shutdown(); } } } }); ``` Incidentally, note that this program catches two different exception types in one catch clause. This facility, informally known as multi-catch, was added in Java 7. It can greatly increase the clarity and reduce the size of programs that behave the same way in response to multiple exception types. 順便提一下,注意這個程序在一個 catch 子句中捕獲了兩種不同的異常類型。這個功能在 Java 7 中添加了,非正式名稱為 multi-catch。它可以極大地提高清晰度,并減少在響應多種異常類型時表現相同的程序的大小。 When we run this program, we don’t get an exception; we get a deadlock. The background thread calls s.removeObserver, which attempts to lock observers, but it can’t acquire the lock, because the main thread already has the lock. All the while, the main thread is waiting for the background thread to finish removing the observer, which explains the deadlock. 當我們運行這個程序時,我們不會得到異常;而是遭遇了死鎖。后臺線程調用 `s.removeObserver`,它試圖鎖定觀察者,但無法獲取鎖,因為主線程已經擁有鎖。一直以來,主線程都在等待后臺線程完成刪除觀察者的操作,這就解釋了死鎖的原因。 This example is contrived because there is no reason for the observer to use a background thread to unsubscribe itself, but the problem is real. Invoking alien methods from within synchronized regions has caused many deadlocks in real systems, such as GUI toolkits. 這個例子是人為設計的,因為觀察者沒有理由使用后臺線程來取消訂閱本身,但是問題是真實的。在實際系統中,從同步區域內調用外來方法會導致許多死鎖,比如 GUI 工具包。 In both of the previous examples (the exception and the deadlock) we were lucky. The resource that was guarded by the synchronized region (observers) was in a consistent state when the alien method (added) was invoked. Suppose you were to invoke an alien method from a synchronized region while the invariant protected by the synchronized region was temporarily invalid. Because locks in the Java programming language are reentrant, such calls won’t deadlock. As in the first example, which resulted in an exception, the calling thread already holds the lock, so the thread will succeed when it tries to reacquire the lock, even though another conceptually unrelated operation is in progress on the data guarded by the lock. The consequences of such a failure can be catastrophic. In essence, the lock has failed to do its job. Reentrant locks simplify the construction of multithreaded object-oriented programs, but they can turn liveness failures into safety failures. 在前面的兩個例子中(異常和死鎖),我們都很幸運。調用外來方法(added)時,由同步區域(觀察者)保護的資源處于一致狀態。假設你要從同步區域調用一個外來方法,而同步區域保護的不變量暫時無效。因為 Java 編程語言中的鎖是可重入的,所以這樣的調用不會死鎖。與第一個導致異常的示例一樣,調用線程已經持有鎖,所以當它試圖重新獲得鎖時,線程將成功,即使另一個概念上不相關的操作正在對鎖保護的數據進行中。這種失敗的后果可能是災難性的。從本質上說,這把鎖沒能發揮它的作用。可重入鎖簡化了多線程面向對象程序的構造,但它們可以將活動故障轉化為安全故障。 Luckily, it is usually not too hard to fix this sort of problem by moving alien method invocations out of synchronized blocks. For the notifyElementAdded method, this involves taking a “snapshot” of the observers list that can then be safely traversed without a lock. With this change, both of the previous examples run without exception or deadlock: 幸運的是,通過將外來方法調用移出同步塊來解決這類問題通常并不難。對于 notifyElementAdded 方法,這涉及到獲取觀察者列表的「快照」,然后可以在沒有鎖的情況下安全地遍歷該列表。有了這個改變,前面的兩個例子都可以再也不會出現異常或者死鎖了: ``` // Alien method moved outside of synchronized block - open calls private void notifyElementAdded(E element) { List<SetObserver<E>> snapshot = null; synchronized(observers) { snapshot = new ArrayList<>(observers); } for (SetObserver<E> observer :snapshot) observer.added(this, element); } ``` In fact, there’s a better way to move the alien method invocations out of the synchronized block. The libraries provide a concurrent collection (Item 81) known as CopyOnWriteArrayList that is tailor-made for this purpose. This List implementation is a variant of ArrayList in which all modification operations are implemented by making a fresh copy of the entire underlying array. Because the internal array is never modified, iteration requires no locking and is very fast. For most uses, the performance of CopyOnWriteArrayList would be atrocious, but it’s perfect for observer lists, which are rarely modified and often traversed. 實際上,有一種更好的方法可以將外來方法調用移出同步塊。庫提供了一個名為 CopyOnWriteArrayList 的并發集合([Item-81](/Chapter-11/Chapter-11-Item-81-Prefer-concurrency-utilities-to-wait-and-notify.md)),該集合是為此目的量身定制的。此列表實現是 ArrayList 的變體,其中所有修改操作都是通過復制整個底層數組來實現的。因為從不修改內部數組,所以迭代不需要鎖定,而且速度非常快。如果大量使用,CopyOnWriteArrayList 的性能會很差,但是對于很少修改和經常遍歷的觀察者列表來說,它是完美的。 The add and addAll methods of ObservableSet need not be changed if the list is modified to use CopyOnWriteArrayList. Here is how the remainder of the class looks. Notice that there is no explicit synchronization whatsoever: 如果將 list 修改為使用 CopyOnWriteArrayList,則不需要更改 ObservableSet 的 add 和 addAll 方法。下面是類的其余部分。請注意,沒有任何顯式同步: ``` // Thread-safe observable set with CopyOnWriteArrayList private final List<SetObserver<E>> observers =new CopyOnWriteArrayList<>(); public void addObserver(SetObserver<E> observer) { observers.add(observer); } public boolean removeObserver(SetObserver<E> observer) { return observers.remove(observer); } private void notifyElementAdded(E element) { for (SetObserver<E> observer : observers) observer.added(this, element); } ``` An alien method invoked outside of a synchronized region is known as an open call [Goetz06, 10.1.4]. Besides preventing failures, open calls can greatly increase concurrency. An alien method might run for an arbitrarily long period. If the alien method were invoked from a synchronized region, other threads would be denied access to the protected resource unnecessarily. 在同步區域之外調用的外來方法稱為 open call [Goetz06, 10.1.4]。除了防止失敗之外,開放調用還可以極大地提高并發性。一個陌生的方法可以運行任意長的時間。如果從同步區域調用了外來方法,其他線程對受保護資源的訪問就會遭到不必要的拒絕。 **As a rule, you should do as little work as possible inside synchronized regions.** Obtain the lock, examine the shared data, transform it as necessary, and drop the lock. If you must perform some time-consuming activity, find a way to move it out of the synchronized region without violating the guidelines in Item 78. **作為規則,你應該在同步區域內做盡可能少的工作。** 獲取鎖,檢查共享數據,根據需要進行轉換,然后刪除鎖。如果你必須執行一些耗時的活動,請設法將其移出同步區域,而不違反 [Item-78](/Chapter-11/Chapter-11-Item-78-Synchronize-access-to-shared-mutable-data.md) 中的指導原則。 The first part of this item was about correctness. Now let’s take a brief look at performance. While the cost of synchronization has plummeted since the early days of Java, it is more important than ever not to oversynchronize. In a multicore world, the real cost of excessive synchronization is not the CPU time spent getting locks; it is contention: the lost opportunities for parallelism and the delays imposed by the need to ensure that every core has a consistent view of memory. Another hidden cost of oversynchronization is that it can limit the VM’s ability to optimize code execution. 本條目的第一部分是關于正確性的。現在讓我們簡要地看一下性能。雖然自 Java 早期以來,同步的成本已經大幅下降,但比以往任何時候都更重要的是:不要過度同步。在多核世界中,過度同步的真正代價不是獲得鎖所花費的 CPU 時間;這是一種爭論:而是失去了并行化的機會,以及由于需要確保每個核心都有一個一致的內存視圖而造成的延遲。過度同步的另一個隱藏成本是,它可能限制 VM 優化代碼執行的能力。 If you are writing a mutable class, you have two options: you can omit all synchronization and allow the client to synchronize externally if concurrent use is desired, or you can synchronize internally, making the class thread-safe (Item 82). You should choose the latter option only if you can achieve significantly higher concurrency with internal synchronization than you could by having the client lock the entire object externally. The collections in java.util (with the exception of the obsolete Vector and Hashtable) take the former approach, while those in java.util.concurrent take the latter (Item 81). 如果你正在編寫一個可變的類,你有兩個選擇:你可以省略所有同步并允許客戶端在需要并發使用時在外部進行同步,或者你可以在內部進行同步,從而使類是線程安全的([Item-82](/Chapter-11/Chapter-11-Item-82-Document-thread-safety.md))。只有當你能夠通過內部同步實現比通過讓客戶端在外部鎖定整個對象獲得高得多的并發性時,才應該選擇后者。`java.util` 中的集合(廢棄的 Vector 和 Hashtable 除外)采用前一種方法,而 `java.util.concurrent` 中的方法則采用后者([Item-81](/Chapter-11/Chapter-11-Item-81-Prefer-concurrency-utilities-to-wait-and-notify.md))。 In the early days of Java, many classes violated these guidelines. For example, StringBuffer instances are almost always used by a single thread, yet they perform internal synchronization. It is for this reason that StringBuffer was supplanted by StringBuilder, which is just an unsynchronized StringBuffer. Similarly, it’s a large part of the reason that the thread-safe pseudorandom number generator in java.util.Random was supplanted by the unsynchronized implementation in java.util.concurrent.ThreadLocalRandom. When in doubt, do not synchronize your class, but document that it is not thread-safe. 在 Java 的早期,許多類違反了這些準則。例如,StringBuffer 實例幾乎總是由一個線程使用,但是它們執行內部同步。正是由于這個原因,StringBuffer 被 StringBuilder 取代,而 StringBuilder 只是一個未同步的 StringBuffer。類似地,同樣,`java.util.Random` 中的線程安全偽隨機數生成器被 `java.util.concurrent.ThreadLocalRandom` 中的非同步實現所取代,這也是原因之一。如果有疑問,不要同步你的類,但要記錄它不是線程安全的。 If you do synchronize your class internally, you can use various techniques to achieve high concurrency, such as lock splitting, lock striping, and nonblocking concurrency control. These techniques are beyond the scope of this book, but they are discussed elsewhere [Goetz06, Herlihy08]. 如果你在內部同步你的類,你可以使用各種技術來實現高并發性,例如分拆鎖、分離鎖和非阻塞并發控制。這些技術超出了本書的范圍,但是在其他地方也有討論 [Goetz06, Herlihy08]。 If a method modifies a static field and there is any possibility that the method will be called from multiple threads, you must synchronize access to the field internally (unless the class can tolerate nondeterministic behavior). It is not possible for a multithreaded client to perform external synchronization on such a method, because unrelated clients can invoke the method without synchronization. The field is essentially a global variable even if it is private because it can be read and modified by unrelated clients. The nextSerialNumber field used by the method generateSerialNumber in Item 78 exemplifies this situation. 如果一個方法修改了一個靜態字段,并且有可能從多個線程調用該方法,則必須在內部同步對該字段的訪問(除非該類能夠容忍不確定性行為)。多線程客戶端不可能對這樣的方法執行外部同步,因為不相關的客戶端可以在不同步的情況下調用該方法。字段本質上是一個全局變量,即使它是私有的,因為它可以被不相關的客戶端讀取和修改。[Item-78](/Chapter-11/Chapter-11-Item-78-Synchronize-access-to-shared-mutable-data.md) 中的 generateSerialNumber 方法使用的 nextSerialNumber 字段演示了這種情況。 In summary, to avoid deadlock and data corruption, never call an alien method from within a synchronized region. More generally, keep the amount of work that you do from within synchronized regions to a minimum. When you are designing a mutable class, think about whether it should do its own synchronization. In the multicore era, it is more important than ever not to oversynchronize. Synchronize your class internally only if there is a good reason to do so, and document your decision clearly (Item 82). 總之,為了避免死鎖和數據損壞,永遠不要從同步區域內調用外來方法。更一般地說,將你在同步區域內所做的工作量保持在最小。在設計可變類時,請考慮它是否應該執行自己的同步。在多核時代,比以往任何時候都更重要的是不要過度同步。只有在有充分理由時,才在內部同步類,并清楚地記錄你的決定([Item-82](/Chapter-11/Chapter-11-Item-82-Document-thread-safety.md))。 --- **[Back to contents of the chapter(返回章節目錄)](/Chapter-11/Chapter-11-Introduction.md)** - **Previous Item(上一條目):[Item 78: Synchronize access to shared mutable data(對共享可變數據的同步訪問)](/Chapter-11/Chapter-11-Item-78-Synchronize-access-to-shared-mutable-data.md)** - **Next Item(下一條目):[Item 80: Prefer executors, tasks, and streams to threads(Executor、task、流優于直接使用線程)](/Chapter-11/Chapter-11-Item-80-Prefer-executors,-tasks,-and-streams-to-threads.md)**
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看