01. Предисловие
Как ни стыдно это говорить: ветеран ИТ с десятилетним стажем и новичок в Java. Сегодня я только что узнал о Java и отказоустойчивости. Я должен вздохнуть, действительно нет конца обучению. Пока вы готовы учиться, в глазах других будет много «старых» знаний, а для меня все новое.
Что можно сделать? Помимо того, что вам стыдно, вы можете только быстро посвятить себя обучению и овладеть этими знаниями.
Чтобы сохранить здание, мы должны переместить абзац английского языка, чтобы объяснить отказоустойчивость.
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.
Если вам это не понравится, я воспользуюсь своими плохими способностями к английскому, чтобы перевести это. В конкретном бою, если политрук обнаружит, что командир в беспорядке, он немедленно сообщит об этом в Центральную военную комиссию с вышестоящим органом - это может эффективно избежать более серьезных последствий. Конечно, если командир Ли Юньлун, отчет бесполезен.
Однако в мире Java нет Ли Юньлуна. Fail-fast играет роль политрука, после того, как об этом доложено начальству, последующие действия выполняться не будут.
Как это связано с кодом? Посмотрите на код ниже.
public void test(Wanger wanger) {
if (wanger == null) {
throw new RuntimeException("wanger 不能为空");
}
System.out.println(wanger.toString());
}
Как только обнаруживается, что Вангер имеет значение null, немедленно генерируется исключение, и вызывающая сторона может решить, что делать в этом случае, следующий шагwanger.toString()
Он не будет выполнен - чтобы избежать более серьезной ошибки, потому что код слишком прост, не проявляется, будет упомянуто позже.
Смотри, отказоустойчивый это призрак, ничего таинственного. Если посмотреть исходный код подробнее, то примеров тому столько же, сколько и голова туристического пика.
А потом ушел? Три секунды, не волнуйтесь, продолжим.
02. Операция удаления набора для каждого
Длительное время,яЯ не понимаю, почему я не могуfor each
Элементы в цикле. Сегодня мы возьмем возможность испытать ее.
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (String str : list) {
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
System.out.println(list);
Этот код выглядит нормально, но работает ужасно.
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.cmower.java_demo.str.Cmower3.main(Cmower3.java:14)
для Мао?
03. Убойный инструмент для анализа проблем
В настоящее время вы можете только просматривать исходный код, 909 строк кода в ArrayList.java примерно такие.
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
То есть, когда выполняется удалениеcheckForComodification
метод, который сравнивает modCount и ожидаемыйModCount, обнаруживает, что они не равны, и выдаетConcurrentModificationException
аномальный.
Почему это будет реализованоcheckForComodification
метод? Это нужно декомпилироватьfor each
Этот код исчез.
List<String> list = new ArrayList();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator var3 = list.iterator();
while (var3.hasNext()) {
String str = (String) var3.next();
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
System.out.println(list);
оригинальныйfor each
Это реализовано через итератор Iterator с циклом while.
1)ArrayList.iterator()
Возвращенный Iterator на самом деле является внутренним классом Itr ArrayList.
public Iterator<E> iterator() {
return new Itr();
}
Itr реализует интерфейс Iterator.
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
то естьnew Itr()
Когда expectModCount присваивается modCount, а modCount является переменной-членом List, указывающей, сколько раз коллекция была изменена. Так как список выполнил метод add 3 раза до этого, значение modCount равно 3; значение ожидаемого ModCount также равно 3.
Исполняемыйlist.remove(str)
После этого значение modCount становится равным 4.
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
}
Примечание. Метод fastRemove вызывается внутри метода удаления.
Следующий цикл выполненString str = (String) var3.next();
Когда это называетсяcheckForComodification
метод, в настоящее время один из них равен 3, а другой — 4, поэтому создается исключение ConcurrentModificationException.
Если вы мне не верите, вы можете напрямую отладить с помощью точки останова в строке 909 класса ArrayList.
Да, одному 4, другому 3.
в заключении. существуетfor each
В цикле обход коллекции фактически реализуется с помощью итератора Iterator в сочетании с циклом while, но метод удаления элемента напрямую используется самим классом коллекции. Это приводит к тому, что итератор обнаруживает, что элемент был изменен без его ведома, когда он проходит, и ему трудно это принять, и он выдает исключение.
Читатель друзей, ты думаешь обо мне, чтобы бежать, потерпеть неудачу иfor each
При чем здесь операция удаления коллекции?
имеют! Итератор использует отказоустойчивый механизм защиты.
04. Как избежать отказоустойчивого механизма защиты?
Благодаря приведенному выше анализу, я думаю, все понимают, почему невозможноfor each
Элемент удаляется в цикле.
Так как же избежать отказоустойчивого механизма защиты? Ведь удаление элементов — рутинная операция, и мы не можем отказаться от еды из-за того, что поперхнулись.
1) сломать после удаления
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (String str : list) {
if ("沉默王二".equals(str)) {
list.remove(str);
break;
}
}
Почему я такой умный, я не могу не гордиться. Вы не понимаете, почему? Затем можно проанализировать приведенный выше анализ исходного кода, подняться на пол и посмотреть на него!
Чуть приоткрою причину: после break цикл уже не будет проходиться, а значит следующий метод Итератора уже не выполняется, а значитcheckForComodification
Метод больше не выполняется, поэтому исключение не будет выброшено.
Однако, когда в списке есть повторяющиеся элементы, которые нужно удалить, разрыв не подходит.
2) для петли
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (int i = 0, n = list.size(); i < n; i++) {
String str = list.get(i);
if ("沉默王二".equals(str)) {
list.remove(str);
}
}
Несмотря на то, что цикл for позволяет избежать механизма отказоустойчивой защиты, то есть после удаления элемента исключение не будет выдано, тем не менее, эта программа в принципе проблематична. Зачем?
В первом цикле i равно 0,list.size()
равно 3, когда выполняется метод удаления, i равно 1,list.size()
Но становится 2, потому что после удаления изменился размер списка, а это значит, что элемент "Silent King Three" был пропущен. Ты понимаешь?
перед удалениемlist.get(1)
для «Silk King Three», но после удаленияlist.get(1)
стал "о каком интересном программисте писать", иlist.get(0)
Станьте «Безмолвным Королем Тройки».
3) Итератор
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator<String> itr = list.iterator();
while (itr.hasNext()) {
String str = itr.next();
if ("沉默王二".equals(str)) {
itr.remove();
}
}
Зачем использовать метод удаления Iterator, чтобы избежать отказоустойчивого механизма защиты? Взгляните на исходный код удаления, чтобы понять.
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
Хотя метод удаления ArrayList по-прежнему используется для удаления элементов, он будет выполняться после удаления.expectedModCount = modCount
, что гарантирует синхронизацию ожидаемого ModCount с modCount.
05. Наконец
В Java отказоустойчивость узко определена для итераторов коллекций в многопоточных ситуациях. Это можно получить изConcurrentModificationException
виден по определению.
This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.
For example, it is not generally permissible for one thread to modify a Collectionwhile another thread is iterating over it. In general, the results of theiteration are undefined under these circumstances. Some Iteratorimplementations (including those of all the general purpose collection implementationsprovided by the JRE) may choose to throw this exception if this behavior isdetected. Iterators that do this are known as fail-fast iterators,as they fail quickly and cleanly, rather that risking arbitrary,non-deterministic behavior at an undetermined time in the future.
Опять плохой перевод.
Это исключение может быть выдано из-за обнаружения одновременной модификации объекта, что не допускается.
Обычно такие операции запрещены, например, один поток модифицирует коллекцию, а другой поток перебирает ее. В этом случае результат итерации неопределен. Некоторые итераторы (такие как внутренний класс ArrayList Itr) предпочитают выдавать это исключение при обнаружении такого поведения. Такие итераторы называются отказоустойчивыми итераторами, потому что отказ на раннем этапе лучше, чем риск неопределенности в будущем.
Поскольку это для многопоточности, почему наши предыдущие анализы основаны на однопоточности? Потому что в широком смысле отказоустойчивость относится к конструкции, которая немедленно прерывает выполнение при возникновении исключения или ошибки.С точки зрения однопоточности это легче понять каждому.
ты так думаешь?
06. Спасибо
Спасибо за чтение, оригинальность не так проста, просто поставьте палец вверх, если вам это нравится, это будет моей самой сильной мотивацией для написания. Если вы считаете, что статья вам немного полезна, она достаточно интересна, просто обратите внимание на мой паблик-аккаунт»Тихий король 2";Ответить"666” Есть также высококачественные обучающие видео 500G для отправки (по категориям).