Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is fail-fast?

2025-03-31 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article is about what fail-fast is. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.

What is fail-fast?

First, let's take a look at Wikipedia's explanation of fail-fast:

In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.

In system design, a quick failure system is a system that can immediately report anything that may indicate a failure. Quick failure systems are usually designed to stop normal operations rather than trying to continue a process that may be defective. This design usually checks the status of the system at multiple points in the operation, so any faults can be detected early. The responsibility of the quick failure module is to detect errors and then let the next highest level of the system handle the errors.

In fact, this is an idea, to put it bluntly, it is to consider the abnormal situation when designing the system, and once an exception occurs, it will be stopped and reported directly.

Take the simplest example of fail-fast:

Public int divide (int divisor,int dividend) {if (dividend = = 0) {throw new RuntimeException ("dividend can't be null");} return divisor/dividend;}

The above code is a method of dividing two integers. In the divide method, we do a simple check on the divisor. If the value is 0, then we directly throw an exception and clearly indicate the cause of the exception. This is actually the practical application of the concept of fail-fast.

The advantage of this is that some errors can be identified in advance, on the one hand, the execution of complex other code can be avoided, on the other hand, after this exception is identified, it can also be dealt with separately.

Well, now you know fail-fast, in fact, he is not mysterious, you may often use in your daily code.

Since fail-fast is a good mechanism, why does the title of the article say that fail-fast will have a hole?

The reason is that the fail-fast mechanism is used in the collection class of Java. If it is not used properly, the code designed by the fail-fast mechanism will be triggered and unexpected conditions will occur.

Fail-fast in the collection class

By default, the fail-fast mechanism in Java refers to an error detection mechanism of the Java collection. When multiple threads make structural changes to parts of the collection, it is possible to generate a fail-fast mechanism, which throws a ConcurrentModificationException (CME is used later).

CMException, which is thrown when the method detects concurrent modifications to the object but does not allow such modifications.

Many times because CMException is thrown in the code, many programmers will be confused about why they throw such concurrency-related exceptions when their code is not executed in a multithreaded environment. Under what circumstances will this situation be thrown out? Let's make an in-depth analysis.

Abnormal recurrence

In Java, if you perform an element remove/add operation on some collection elements in the foreach loop, the fail-fast mechanism is triggered, which in turn throws a CMException.

Such as the following code:

List userNames = new ArrayList () {{add ("Hollis"); add ("hollis"); add ("HollisChuang"); add ("H");}}; for (String userName: userNames) {if (userName.equals ("Hollis")) {userNames.remove (userName);}} System.out.println (userNames)

The above code uses the enhanced for to loop through the element and try to remove the Hollis string element from it. When you run the above code, the following exception is thrown:

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification (ArrayList.java:909) at java.util.ArrayList$Itr.next (ArrayList.java:859) at com.hollis.ForEach.main (ForEach.java:22)

Similarly, the reader can try to add elements using the add method in the enhanced for loop, and the result will be the same exception.

Before going into the principle, let's try to paraphrase foreach to see how foreach is implemented.

Using the jad tool, we decompiled the compiled class and got the following code:

Public static void main (String [] args) {/ / initialize a List List userNames = new ArrayList () {{add ("Hollis"); add ("hollis"); add ("HollisChuang"); add ("H");}}; Iterator iterator = userNames.iterator (); do {if (! iterator.hasNext () break; String userName = (String) iterator.next (); if (userName.equals ("Hollis")) userNames.remove (userName) } while (true); System.out.println (userNames);}

You can see that foreach actually relies on while loops and Iterator implementations.

Abnormal principle

Through the exception stack of the above code, we can trace that the code that actually threw the exception is:

Java.util.ArrayList$Itr.checkForComodification (ArrayList.java:909)

This method is called in the iterator.next () method. Let's take a look at the implementation of this method:

Final void checkForComodification () {if (modCount! = expectedModCount) throw new ConcurrentModificationException ();}

As above, modCount and expectedModCount are compared in this method, and if they don't want to wait, CMException is thrown.

So, what are modCount and expectedModCount? What is the reason why they don't want to wait?

ModCount is a member variable in ArrayList. It represents the number of times the collection has actually been modified.

List userNames = new ArrayList () {{add ("Hollis"); add ("hollis"); add ("HollisChuang"); add ("H");}}

This variable is available when the collection is initialized with the above code. The initial value is 0.

ExpectedModCount is a member variable in Itr, an inner class in ArrayList.

Iterator iterator = userNames.iterator ()

In the above code, you get an Itr class that implements the Iterator interface.

ExpectedModCount indicates the number of times the iterator expects the collection to be modified. Its value is initialized as the Itr is created. This value changes only if the collection is manipulated through an iterator.

So, then let's take a look at what is done in the userNames.remove (userName); method and why it causes the values of expectedModCount and modCount to be different.

By looking through the code, we can also find that the core logic of the remove method is as follows:

Private void fastRemove (int index) {modCount++; int numMoved = size-index-1; if (numMoved > 0) System.arraycopy (elementData, index+1, elementData, index, numMoved); elementData [--size] = null; / / clear to let GC do its work}

As you can see, it only modifies the modCount and does nothing to expectedModCount.

Simply draw a picture to describe the above scene:

To sum up, the CMException exception is thrown because the enhanced for loop is used in our code, and in the enhanced for loop, the collection traversal is done through iterator, but the add/remove of the element is directly used by the collection class's own methods. As a result, when iterator traverses, it will find that an element has been deleted / added unwittingly, and an exception will be thrown to remind the user that concurrent changes may have occurred!

Therefore, when using the collection class of Java, if CMException occurs, priority should be given to the situation related to fail-fast. In fact, concurrency does not really occur here, but Iterator uses the protection mechanism of fail-fast. As long as he finds that a change has not been made by himself, he will throw an exception.

As to how to solve this problem, we introduced it in "Why Alibaba forbids the remove/add operation of elements in the foreach loop," and I won't repeat it here.

Fail-safe

To avoid triggering the fail-fast mechanism and causing exceptions, we can use some of the collection classes provided in Java that use the fail-safe mechanism.

When traversing, such a collection container is not accessed directly on the collection content, but first copies the original collection content and traverses the copied collection.

The containers under the java.util.concurrent package are all fail-safe and can be used concurrently and modified concurrently under multithreading. At the same time, add/remove can be done in foreach.

Let's take CopyOnWriteArrayList, a collection class of fail-safe, for a brief analysis.

Public static void main (String [] args) {List userNames = new CopyOnWriteArrayList () {{add ("Hollis"); add ("hollis"); add ("HollisChuang"); add ("H");}; userNames.iterator (); for (String userName: userNames) {if (userName.equals ("Hollis")) {userNames.remove (userName);}} System.out.println (userNames);}

The above code, using CopyOnWriteArrayList instead of ArrayList, will not cause an exception.

All changes to the fail-safe collection are made by copying a copy and then on the copy collection, not directly to the original collection. And these modification methods, such as add/remove, control concurrency by locking.

Therefore, the iterator in CopyOnWriteArrayList does not need to do concurrency detection of fail-fast during the iteration. (because the main purpose of fail-fast is to identify concurrency and then notify the user in an abnormal way)

However, while the advantage of copy-based content is to avoid ConcurrentModificationException, the iterator does not have access to the modified content. Such as the following code:

Public static void main (String [] args) {List userNames = new CopyOnWriteArrayList () {{add ("Hollis"); add ("hollis"); add ("HollisChuang"); add ("H");}}; Iterator it = userNames.iterator (); for (String userName: userNames) {if (userName.equals ("Hollis")) {userNames.remove (userName);}} System.out.println (userNames) While (it.hasNext ()) {System.out.println (it.next ());}}

After we get the Iterator of CopyOnWriteArrayList, we delete the values in the original array directly through the for loop, and finally output Iterator at the end. The results are as follows:

[hollis, HollisChuang, H]

Hollis

Hollis

HollisChuang

H

The iterator traverses the copy of the collection obtained at the beginning of the traversal, and the iterator is unaware of the changes that occur in the original collection during the traversal.

Copy-On-Write

After learning about CopyOnWriteArrayList, I don't know if you will have such a question: his add/remove and other methods have been locked, why do you need copy to modify them? carry coals to newcastle? It's also a thread-safe collection. What's the difference between this and Vector?

Copy-On-Write, abbreviated as COW, is an optimization strategy used in programming. The basic idea is that everyone is sharing the same content from the beginning, and when someone wants to modify it, they will actually Copy the content to form a new content and then change it, which is a lazy strategy for delay.

The CopyOnWrite container is the container that is copied when writing. The popular understanding is that when we add elements to a container, we do not add elements directly to the current container, but first Copy the current container to copy a new container, and then add elements to the new container. After adding elements, we point the reference of the original container to the new container.

Writing methods such as add/remove in CopyOnWriteArrayList need to be locked in order to prevent Copy from sending N copies, resulting in concurrent writing.

However, the read method in CopyOnWriteArrayList is unlocked.

Public E get (int index) {return get (getArray (), index);}

The advantage of this is that we can read the CopyOnWrite container concurrently, of course, the data read here may not be up-to-date. Because the idea of replication at write time is to achieve the ultimate consistency of data through the strategy of deferred update, not strong consistency.

* * so the CopyOnWrite container is a separate thought of reading and writing, and different containers for reading and writing. * * while Vector uses the same container for reading and writing, reading and writing are mutually exclusive, and can only do one thing at the same time.

Thank you for reading! This is the end of this article on "what is fail-fast?". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, you can share it for more people to see!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report