ICU-22582 Avoid synchronizing in RuleBasedBreakIterator and ULocale unless strictly necessary

See #2775
This commit is contained in:
Diego Gutierrez Yepiz 2024-02-20 22:58:59 +00:00 committed by Markus Scherer
parent 8acebe4a0c
commit f3e50a7624
2 changed files with 54 additions and 55 deletions

View file

@ -19,8 +19,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.MissingResourceException;
import com.ibm.icu.impl.CharacterIteration;
@ -57,9 +56,6 @@ public class RuleBasedBreakIterator extends BreakIterator {
*/
private RuleBasedBreakIterator() {
fDictionaryCharCount = 0;
synchronized(gAllBreakEngines) {
fBreakEngines = new ArrayList<>(gAllBreakEngines);
}
}
/**
@ -174,9 +170,6 @@ public class RuleBasedBreakIterator extends BreakIterator {
if (fText != null) {
result.fText = (CharacterIterator)(fText.clone());
}
synchronized (gAllBreakEngines) {
result.fBreakEngines = new ArrayList<>(gAllBreakEngines);
}
result.fLookAheadMatches = new int[fRData.fFTable.fLookAheadResultsSize];
result.fBreakCache = result.new BreakCache(fBreakCache);
result.fDictionaryCache = result.new DictionaryCache(fDictionaryCache);
@ -342,23 +335,20 @@ public class RuleBasedBreakIterator extends BreakIterator {
* Lazily updated as break engines are needed, because instantiation of
* break engines is expensive.
*
* Because gAllBreakEngines can be referenced concurrently from different
* BreakIterator instances, all access is synchronized.
* Important notes:
* <ul>Because we don't want to add the same LanguageBreakEngine multiple times, all writes
* are synchronized.
* <ul>Read access avoids explicit synchronization, but will end up being synchronized if
* needed.
*/
private static final List<LanguageBreakEngine> gAllBreakEngines;
private static final ConcurrentLinkedQueue<LanguageBreakEngine> gAllBreakEngines;
static {
gUnhandledBreakEngine = new UnhandledBreakEngine();
gAllBreakEngines = new ArrayList<>();
gAllBreakEngines = new ConcurrentLinkedQueue<>();
gAllBreakEngines.add(gUnhandledBreakEngine);
}
/**
* List of all known break engines. Similar to gAllBreakEngines, but local to a
* break iterator, allowing it to be used without synchronization.
*/
private List<LanguageBreakEngine> fBreakEngines;
/**
* Dump the contents of the state table and character classes for this break iterator.
* For debugging only.
@ -726,19 +716,18 @@ public class RuleBasedBreakIterator extends BreakIterator {
// We have a dictionary character.
// Does an already instantiated break engine handle it?
for (LanguageBreakEngine candidate : fBreakEngines) {
// First read without synchronization, which could lead to a new language
// break engine being added and we didn't go over it.
for (LanguageBreakEngine candidate : gAllBreakEngines) {
if (candidate.handles(c)) {
return candidate;
}
}
synchronized (gAllBreakEngines) {
// This break iterator's list of break engines didn't handle the character.
// Check the global list, another break iterator may have instantiated the
// desired engine.
// Another break iterator may have instantiated the desired engine.
for (LanguageBreakEngine candidate : gAllBreakEngines) {
if (candidate.handles(c)) {
fBreakEngines.add(candidate);
return candidate;
}
}
@ -791,7 +780,6 @@ public class RuleBasedBreakIterator extends BreakIterator {
if (eng != null && eng != gUnhandledBreakEngine) {
gAllBreakEngines.add(eng);
fBreakEngines.add(eng);
}
return eng;
} // end synchronized(gAllBreakEngines)

View file

@ -545,13 +545,13 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
/**
* Keep our own default ULocale.
*/
private static Locale defaultLocale = Locale.getDefault();
private static ULocale defaultULocale;
private static volatile ULocale defaultULocale;
private static Locale[] defaultCategoryLocales = new Locale[Category.values().length];
private static ULocale[] defaultCategoryULocales = new ULocale[Category.values().length];
static {
Locale defaultLocale = Locale.getDefault();
defaultULocale = forLocale(defaultLocale);
if (JDKLocaleHelper.hasLocaleCategories()) {
@ -581,34 +581,47 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
* @stable ICU 2.8
*/
public static ULocale getDefault() {
// Only synchronize if we must update the default locale.
ULocale currentDefaultULocale = defaultULocale;
if (currentDefaultULocale == null) {
// When Java's default locale has extensions (such as ja-JP-u-ca-japanese),
// Locale -> ULocale mapping requires BCP47 keyword mapping data that is currently
// stored in a resource bundle.
// If this happens during the class initialization's call to .forLocale(defaultLocale),
// then defaultULocale is still null until forLocale() returns.
// However, UResourceBundle currently requires non-null default ULocale.
// For now, this implementation returns ULocale.ROOT to avoid the problem.
// TODO: Consider moving BCP47 mapping data out of resource bundle later.
return ULocale.ROOT;
} else if (currentDefaultULocale.locale.equals(Locale.getDefault())) {
return currentDefaultULocale;
}
synchronized (ULocale.class) {
if (defaultULocale == null) {
// When Java's default locale has extensions (such as ja-JP-u-ca-japanese),
// Locale -> ULocale mapping requires BCP47 keyword mapping data that is currently
// stored in a resource bundle. However, UResourceBundle currently requires
// non-null default ULocale. For now, this implementation returns ULocale.ROOT
// to avoid the problem.
// TODO: Consider moving BCP47 mapping data out of resource bundle later.
return ULocale.ROOT;
}
Locale currentDefault = Locale.getDefault();
if (!defaultLocale.equals(currentDefault)) {
defaultLocale = currentDefault;
defaultULocale = forLocale(currentDefault);
assert currentDefault != null;
if (!JDKLocaleHelper.hasLocaleCategories()) {
// Detected Java default Locale change.
// We need to update category defaults to match
// Java 7's behavior on Android API level 21..23.
for (Category cat : Category.values()) {
int idx = cat.ordinal();
defaultCategoryLocales[idx] = currentDefault;
defaultCategoryULocales[idx] = forLocale(currentDefault);
}
} }
return defaultULocale;
currentDefaultULocale = defaultULocale;
assert currentDefaultULocale != null;
if (currentDefaultULocale.locale.equals(currentDefault)) {
return currentDefaultULocale;
}
ULocale nextULocale = forLocale(currentDefault);
assert nextULocale != null;
if (!JDKLocaleHelper.hasLocaleCategories()) {
// Detected Java default Locale change.
// We need to update category defaults to match
// Java 7's behavior on Android API level 21..23.
for (Category cat : Category.values()) {
int idx = cat.ordinal();
defaultCategoryLocales[idx] = currentDefault;
defaultCategoryULocales[idx] = nextULocale;
}
}
return defaultULocale = nextULocale;
}
}
@ -630,8 +643,7 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
* @stable ICU 3.0
*/
public static synchronized void setDefault(ULocale newLocale){
defaultLocale = newLocale.toLocale();
Locale.setDefault(defaultLocale);
Locale.setDefault(newLocale.toLocale());
defaultULocale = newLocale;
// This method also updates all category default locales
for (Category cat : Category.values()) {
@ -675,8 +687,7 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
// time.
Locale currentDefault = Locale.getDefault();
if (!defaultLocale.equals(currentDefault)) {
defaultLocale = currentDefault;
if (!defaultULocale.locale.equals(currentDefault)) {
defaultULocale = forLocale(currentDefault);
for (Category cat : Category.values()) {