Есть ли общие недостатки использования java .lang.ThreadLocal? - PullRequest
0 голосов
/ 02 марта 2020

Каждый раз, когда вы используете String.format(), создается новый экземпляр java.util.Formatter, который впоследствии будет удален.

Просто потому, что мне было любопытно, я немного поиграл с этим, чтобы использовать format() немного эффективнее, кэшируя объект Formatter. К сожалению, Formatter не является поточно-ориентированным, и любые средства синхронизации могут поглотить любой прирост производительности, который вы можете получить от его кеширования.

Наконец, я нашел решение ниже:

import java.util.Formatter;

    /*------------------------*\
====** Static Initialisations **===========================================
    \*------------------------*/
/**
 *  The initial buffer size that is used by
 *  {@link #format(String, Object...)}
 *  and
 *  {@link #format(Locale, String, Object...)}
 *  (per thread): {@value}.
 */
public final static int FORMAT_INITIAL_BUFFERSIZE = 2048;

/**
 *  The cached
 *  {@link Formatter}
 *  instance.
 */
private static final ThreadLocal<Formatter> m_Formatter;

static
{
    //---* The cached Formatter instance *---------------------------------
    final Supplier<Formatter> initializer = () -> new Formatter( new StringBuilder( FORMAT_INITIAL_BUFFERSIZE ), Locale.getDefault( Locale.Category.FORMAT ) );
    m_Formatter = ThreadLocal.<Formatter>withInitial( initializer );
}

    /*---------*\
====** Methods **==========================================================
    \*---------*/
/**
 *  Returns a formatted String using the specified
 *  {@link Locale},
 *  format String, and arguments.<br>
 *  <br>This method is meant as a replacement for the method
 *  {@link java.lang.String#format(String, Object...)};
 *  that implementation always uses a new instance of
 *  {@link Formatter}
 *  for each invocation. The implementation here uses a cached instance
 *  instead, and as {@code Formatter} is not inherently thread-safe, this
 *  cached instance is created and stored per thread, therefore no
 *  synchronisation is needed.<br>
 *  <br>The performance gain for this implementation is not that impressive
 *  when compared with that of
 *  {@link #format(String, Object...)},
 *  but still in the 10% range.
 *  {@code java.lang.String.format()}.
 *
 *  @param  locale  The
 *      {@link Locale}
 *      that will be applied during formatting. If {@code locale} is
 *      {@code null} then no localisation is applied.
 *  @param  format  A format String with the syntax as described for the
 *      {@link Formatter}
 *      class.
 *  @param  args    The arguments referenced by the format specifiers in
 *      the {@code format} String. If there are more arguments than format
 *      specifiers, the extra arguments are ignored. The number of
 *      arguments is variable and may be zero. The maximum number of
 *      arguments is limited by the maximum dimension of a Java array as
 *      defined by <cite>The Java&trade; Virtual Machine
 *      Specification</cite>. The behaviour on a {@code null} argument
 *      depends on the conversion.
 *  @throws IllegalFormatException A format string contains an illegal
 *      syntax, a format specifier that is incompatible with the given
 *      arguments, insufficient arguments given the format string, or other
 *      illegal conditions occurred. For specification of all possible
 *      formatting errors, see the &quot;Details&quot; section of the
 *      {@code Formatter} class specification.
 *  @return A formatted String.
 *
 *  @see  java.util.Formatter
 */
@SuppressWarnings( "resource" )
public final static String format( final Locale locale, final String format, final Object... args ) throws IllegalFormatException
{
    /*
     * When this method StringUtils.format() is used inside an
     * implementation of Formattable.formatTo() that in turn was called by
     * this method already, the internal out buffer of the formatter
     * contains already some text that has to be preserved.
     *
     * Therefore the current length of the out buffer is kept before new
     * contents is added by the call to formatter.format(), and reset after
     * the new formatted text was retrieved from that buffer.
     */
    final var formatter = m_Formatter.get();
    final var result = (StringBuilder) formatter.out();
    final var currentPos = result.length();
    formatter.format( locale, format, args );
    final var retValue = result.substring( currentPos );
    result.setLength( currentPos );

    //---* Done *----------------------------------------------------------
    return retValue;
}   //  format()

Кэшированный экземпляр Formatter хранится в экземпляре java.lang.ThreadLocal, поэтому каждый поток имеет свой собственный экземпляр, и синхронизация не требуется.

Пока он работает; всесторонние тесты пока не выявили никаких проблем, а некоторые (искусственные) тесты показывают прирост производительности примерно на 10% по сравнению с использованием java.lang.String.format(); это увеличение является постоянным на любом (классе) компьютере, на котором я выполнял тесты.

Очевидным недостатком этого решения является то, что объем памяти для каждого потока, который использует мою реализацию format(), увеличивается примерно на половину. КБ плюс размер исходного буфера (см. источник).

Но я слабо помню некоторые утверждения, что использование java.lang.ThreadLocal является злом , хотя я не могу вспомнить ни одной причины, почему ... в основном , вы можете свести это к "есть слухи ...".

Или нет? Есть ли что-то, что я упустил из-за использования java.util.ThreadLocal, как я это сделал?

Пожалуйста, не обсуждайте, может ли замена String.format() моим решением быть полезной или нет: я просто сделал это - как было сказано - потому что мне было любопытно, можно ли сделать это быстрее, кэшируя экземпляр Formatter - больше ничего.

Редактировать (основываясь на некоторых комментариях, которые были добавлены к вопросу):

  • Предполагается, что экземпляр Formatter никогда не будет удален из потока. Но нет необходимости сохранять размер ассоциированного StringBuilder в его текущем размере, если он был расширен выше исходного размера.
  • Существует проблема с ThreadLocal в контексте «управляемых сред» (Серверы приложений и веб-серверы), поскольку потоки используются повторно, и поэтому данные, хранящиеся в ThreadLocal, никогда не будут собираться, поскольку ThreadLocal никогда не будет освобожден.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...