Это поле.
private static final long BUF_OFFSET
= U.objectFieldOffset(BufferedInputStream.class, "buf");
Это код, использующий BUF_OFFSET.
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) { /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else { /* grow buffer */
int nsz = ArraysSupport.newLength(pos,
1, /* minimum growth */
pos /* preferred growth */);
if (nsz > marklimit)
nsz = marklimit;
byte[] nbuf = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
**if (!U.compareAndSetReference(this, BUF_OFFSET, buffer, nbuf)) {
// Can't replace buf if there was an async close.
// Note: This would need to be changed if fill()
// is ever made accessible to multiple threads.
// But for now, the only way CAS can fail is via close.
// assert buf == null;
throw new IOException("Stream closed");
}**
buffer = nbuf;
}
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
Это код в исходном коде JDK о возврате значения BUF_OFFSET.
static jlong find_field_offset(jclass clazz, jstring name, TRAPS) {
assert(clazz != NULL, "clazz must not be NULL");
assert(name != NULL, "name must not be NULL");
ResourceMark rm(THREAD);
char *utf_name = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(name));
InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
jint offset = -1;
for (JavaFieldStream fs(k); !fs.done(); fs.next()) {
Symbol *name = fs.name();
if (name->equals(utf_name)) {
**offset** = fs.offset();
break;
}
}
if (offset < 0) {
THROW_0(vmSymbols::java_lang_InternalError());
}
return field_offset_from_byte_offset(offset);
}
Что означает поле BUF_OFFSET в BufferedInputStream?
Я проверяю BUF_OFFSET в исходном коде JDK. гитхаб:https://github.com/openjdk/jdk/tree/jdk-17%2B35 Я задал вопрос о том, что здесь находится JavaFieldStream. Но я все еще не понимаю BUF_OFFSET.
Я думаю ... Может быть, BUF_OFFSET похож на параметр off, который находится в методе чтения FileInputStream?
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
Здесь off означает, что данные копируются и заполняются из массива C в массив Java. Таким образом, BUF_OFFSET означает, где данные заполняют JVM? Это только мое предположение.
Исходный код JDK взят из c
В самом начале в BufferedInputStream есть комментарий: «Поскольку этот класс используется на ранней стадии начальной загрузки, он мотивирован использовать Unsafe.compareAndSetObject вместо AtomicReferenceFieldUpdater (или VarHandles), чтобы уменьшить зависимости и сократить время запуска».
@StefanZobel Конечно, я знаю комментарий, я просто хочу узнать о нем больше подробностей.
Что ж, этот комментарий должен, по крайней мере, ответить на ваш вопрос «Почему необходимо использовать метод compareAndSetReference?» вопрос.
Не задавайте два разных вопроса одновременно. «Что означает поле BUF_OFFSET» и «Почему необходимо использовать compareAndSetReference» совершенно не связаны.
@Holger хорошо, я изменил его. Только чтобы сначала спросить, что такое BUF_OFFSET.
Это крайняя деталь реализации, о которой вам не следует беспокоиться, если только вы не собираетесь вносить свой вклад в сам OpenJDK. Как я уже говорил ранее, как начинающий Java-программист, почему вы вообще пытаетесь это прочитать? Вам не нужно читать код реализации Java, чтобы использовать его. Вы, скорее всего, просто потеряетесь в таких деталях, и на самом деле это не научит вас хорошо программировать/использовать Java.
Как уже говорилось в комментарии, процитированном Стефаном Зобелем, этот класс намеренно использует не AtomicReferenceFieldUpdater
и VarHandle
, а низкоуровневый класс с именем Unsafe
для атомарного обновления. Эта альтернатива не инкапсулирует объект Field
или его эквивалент, но требует необработанного смещения в объект, чтобы определить поле (местоположение в памяти) для обновления. Вот почему вы видите BUF_OFFSET = U.objectFieldOffset(BufferedInputStream.class, "buf")
→ найти смещение поля buf
и U.compareAndSetReference(this, BUF_OFFSET, …)
→ обновить поле buf
текущего объекта (this
).
@Holger Могу ли я понять так: у buf есть смещение, у нового более длинного массива nbuf тоже есть смещение. U.compareAndSetReference должен сделать смещение BUF_OFFSET=nbuf, чтобы позволить buf = nuf в следующей строке.
@MarkRotteveel Спасибо за внимание и советы! Мой уровень Java, вероятно, для программирования в Google. Я знаю правила и грамматику Java. Когда я начинаю писать код, я не знаком с функциями в деталях, поэтому сейчас читаю исходный код. И это, без сомнения, вызовет много проблем при чтении, но, к счастью, я нашел сайт-сокровище - stackoverflow. Поэтому я начинаю задавать вопросы здесь.
"U.compareAndSetReference должен сделать BUF_OFFSET=смещение nbuf ...". Нет, совсем нет. Вызов compareAndSetReference
атомарно обновляет переменную-член buf
на nbuf
, если она в настоящее время содержит buffer
. Ничего другого здесь не происходит.
@StefanZobel Но «buffer = nbuf» было написано после «if (! U.compareAndSetReference)», что произойдет, если я удалю оператор if?
«buffer=nbuf» устанавливает buffer
, а не buf
@StefanZobel Я искал слово: «AtomicReferenceFieldUpdater» в Google, и я понимаю, что оно использовалось для изменения значения изменчивого поля. Итак, «compareAndSetReference» делает почти то же самое? Не разрешайте buf=nbuf напрямую, поэтому требуется «compareAndSetReference», чтобы позволить nbuf заменить buf.
Это if buf == buffer buf = nbuf;
, но атомарно. Это как compareAndSet
в AtomicReferenceFieldUpdater. Важно не только то, что поле изменчиво, но и то, что весь процесс сравнения (чтения) поля с ожидаемым значением (buffer
) и, если оно совпадает, установка поля в его новое значение (nbuf
) является atomic
операцией. Как говорится в комментарии, это можно было сделать с помощью AtomicReferenceFieldUpdater за счет загрузки дополнительных классов во время запуска JVM (замедление запуска), и именно этого они хотели избежать, используя небезопасный API.
@StefanZobel Я наконец понял! Спасибо за ваше терпение. Я хочу принять ваш ответ. Хотите ли вы скопировать свои комментарии в столбец ответов, чтобы я мог их принять?
BUF_OFFSET
— это просто смещение поля buf
в объекте BufferedInputStream
, которое требуется для доступа к этому полю с помощью метода Unsafe.compareAndSetReference
. Поскольку использование (неофициально задокументированного) класса sun.misc.Unsafe подразумевает, что это низкоуровневый код, который небезопасно взаимодействует с предположениями об объектах и их доступе.
Этот метод, в свою очередь, используется как альтернатива AtomicReferenceFieldUpdater, как описано в комментарии в начале класса:
Поскольку этот класс используется на ранних этапах начальной загрузки, рекомендуется использовать Unsafe.compareAndSetObject вместо AtomicReferenceFieldUpdater (или VarHandles), чтобы уменьшить количество зависимостей и сократить время запуска.
Метод fill()
заполняет внутренний элемент buf
дополнительными данными, заменяя его другим массивом другого размера, когда это необходимо.
Вызовы fill() защищены внутренней блокировкой (или синхронизированы, если BufferedInputStream является подклассом). Когда InputStream закрывается, для buf
устанавливается значение null
, чтобы указать, что поток закрыт.
Однако вызовы close()
могут быть асинхронными в том смысле, что функция close() не защищена от одновременного доступа, как вызовы fill()
.
Следовательно, возникает необходимость проверить, является ли buf == null
(поток закрыт) в fill()
, и выполнить if (buf == buffer) buf = null;
(buf
не был изменен одновременно fill()) в close()
потокобезопасным (атомарным) способом.
Это делается с помощью атомарных инструкций CAS (сравнить и поменять местами / сравнить и установить), которые сравнивают содержимое ячейки памяти с заданным значением и, только если они совпадают, изменяют содержимое этой ячейки на присваивается новое значение как одна атомарная операция. Атомарность гарантирует, что запись зависит от того, является ли текущее значение актуальным (т. е. не измененным тем временем другим потоком).
В Java это можно сделать несколькими способами: классы AtomicXyzFieldUpdater в пакете j.u.concurrent.atomic, API j.l.invoke VarHandle, представленный в Java 9, и внутренний Unsafe
API JDK, не предназначенный для публичного использования.
Поскольку BufferedInputStream используется на ранней стадии начальной загрузки JVM (это означает фазу инициализации при запуске JVM), важно: а) избегать использования API, которые могли не быть инициализированы в то время (что может привести к циклам зависимости) и б) избегать замедляет начальную загрузку с помощью API, которые должны быть сначала инициализированы, чтобы их можно было использовать. В этом причина использования внутреннего Unsafe
API.
Небезопасный метод, о котором мы говорим, заключается в следующем:
/**
* Atomically updates Java variable to {@code x} if it is currently
* holding {@code expected}.
*
* <p>This operation has memory semantics of a {@code volatile} read
* and write. Corresponds to C11 atomic_compare_exchange_strong.
*
* @return {@code true} if successful
*/
@IntrinsicCandidate
public final native boolean compareAndSetReference(Object o, long offset,
Object expected,
Object x);
Первый аргумент — это экземпляр объекта, поле которого должно быть установлено, второй аргумент — это смещение поля, которое должно быть обновлено в этом объекте (по сути, относительный адрес памяти), третий аргумент — это значение, которое мы ожидаем в настоящее время. это поле, а четвертый аргумент — это значение, которое мы хотим сохранить в этом поле, если наше ожидание окажется правильным.
Смещение поля можно определить с помощью
/**
* Reports the location of the field with a given name in the storage
* allocation of its class.
*
* @throws NullPointerException if any parameter is {@code null}.
* @throws InternalError if there is no field named {@code name} declared
* in class {@code c}, i.e., if {@code c.getDeclaredField(name)}
* would throw {@code java.lang.NoSuchFieldException}.
*
* @see #objectFieldOffset(Field)
*/
public long objectFieldOffset(Class<?> c, String name) {
if (c == null || name == null) {
throw new NullPointerException();
}
return objectFieldOffset1(c, name);
}
Смещение поля — это константа, которую необходимо определить только один раз, и обычно она хранится в static final long
. Это и есть BUF_OFFSET (смещение поля buf
внутри экземпляра BufferedInputStream).
Итак, (потокобезопасный) код
if (!U.compareAndSetReference(this, BUF_OFFSET, buffer, nbuf)) {
throw new IOException("Stream closed");
}
логически эквивалентен (однопоточному) коду
if (buf == buffer) {
buf = nbuf;
} else {
throw new IOException("Stream closed");
}
Единственная разница в том, что compareAndSetReference
является атомарным, а последний код — нет.
Идеальный ответ!
Не спамить тегами. Как C был связан с этим вопросом