Java Theory and Practice: Make a good (event) listener

Source: Internet
Author: User
Tags concurrentmodificationexception

Observer mode is common in Swing development, and is useful for eliminating the coupling of components in scenarios other than GUI applications. However, there are still some common flaws in the registration and invocation of listeners. In this period of Java theory and practice, Java expert Brian Goetz offers some good advice on how to make a good listener and how to be friendly to your listeners. Share your thoughts about this article with the author and other readers at the appropriate discussion forum. (You can also click the Discussion Access forum at the top or bottom of this article.) )

The Swing framework exploits the Observer pattern (also known as the publish-subscribe model) in the form of event listeners. Swing Components act as targets for user interaction, triggering events when users interact with them, and data model classes that trigger events when data changes. Using the observer in this way allows the controller to detach from the model, allowing the model to detach from the view, simplifying the development of the GUI application.

The "gang" design pattern (see resources) describes the observer pattern as defining "one-to-many" relationships between objects, so that when an object changes state, all its dependencies are notified and automatically updated. The Observer pattern supports loose coupling between components, and components can facilitate the reuse of components by keeping their state synchronized without having to know each other's identities or internal conditions directly.

AWT and Swing components, such as JButton or JTable, use the observer pattern to eliminate the coupling between GUI event generation and their semantics in the specified application. Similarly, Swing model classes, such as TableModel and TreeModel, also use the observer to eliminate the coupling between the data model representation and the view generation, thus supporting multiple independent views of the same data. Swing defines the event and EventListener object hierarchies, the components that can generate events, such as JButton (visual components) or TableModel (data model), providing Addxxxlistener () and Removexxxlist Ener () method for registering and canceling registrations for listeners. These classes are responsible for deciding when they need to trigger events, when they do trigger events, and when to call all registered listeners.

To support listeners, objects need to maintain a list of registered listeners, provide the means for listeners to register and cancel registrations, and call each listener when the appropriate event occurs. It is easy to use and support listeners (not just in GUI applications), but you should avoid some defects on both sides of the registration interface, which are components that support listeners and those that register listeners.

Thread safety issues

Typically, the thread that calls the listener differs from the thread that registers the listener. To support registering listeners from different threads, this mechanism must be thread safe regardless of the mechanism by which the active listener list is stored and managed. Many of the examples in Sun's documentation use Vector to save the listener list, which solves some of the problems but does not solve all the problems. When an event is triggered, the component that triggers it considers the list of iterator listeners and invokes each listener, which poses the risk of concurrent modifications, such as when a thread accidentally wants to add or remove a listener during the listener list iteration.

Manage Listeners list

Suppose you use vector<listener> to save a list of listeners. Although the Vector class is thread-safe (meaning that it can be invoked without additional synchronization), there is no risk of damaging the Vector data structure, but the iteration of the collection contains the "Detect and execute" sequence, and if the collection is modified during the iteration, there is a risk of failure. Suppose that there are three listeners in the list at the start of the iteration. When iterating over vectors, call the size () and get () methods repeatedly until all the elements are retrieved, as shown in Listing 1:

Listing 1. The unsafe iterations of vectorsVector<Listener> v;
for (int i=0; i<v.size(); i++)
  v.get(i).eventHappened(event);

But what happens if someone deletes a listener from the list exactly after the last call to Vector.size ()? Now, Vector.get () returns Null (this is true because its state has changed since the last time the Vector was detected) and throws NullPointerException when trying to invoke eventhappened (). This is an example of a "detect and then execute" sequence--detect if there are more elements, and if so, take the next element--but in the case of concurrent modifications, the State may have changed after detection. Figure 1 illustrates this problem:

Figure 1. Concurrent iterations and modifications, resulting in unexpected failures

One solution to this problem is to hold the lock on the vector during the iteration, and the other is to clone the vector or call its ToArray () method to retrieve its contents each time an event occurs. All of these methods have a performance problem: the first risk is that during the iteration, the other locks that want to access the listener list are outside, the second creates a temporary object, and the list is copied every time the event occurs.

If you use an iterator (iterator) to traverse the list of listeners, there will be the same problem, just slightly different; iterator () implementation does not throw NullPointerException, which is thrown when the collection changes after it detects the beginning of the iteration Concurrentmodificationexception. Similarly, you can prevent this problem by locking the collection during the iteration.

The Copyonwritearraylist class in Java.util.concurrent can help prevent this problem. It implements the List and is thread-safe, but its iterator does not throw concurrentmodificationexception, and no additional locks are required during traversal. This combination of attributes is achieved by reallocating and copying the contents of the list internally each time the list is modified, so that threads that traverse the content do not need to handle changes-from their perspective, the contents of the list remain unchanged during traversal. While this may sound inefficient, keep in mind that in most observers, each component has only a small number of listeners, which far exceeds the number of insertions and deletions. So faster iterations can compensate for slower change processes and provide better concurrency because multiple threads can iterate the list at the same time.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.