Skip to content

Commit f3e50a7

Browse files
Diego1149markusicu
authored andcommitted
ICU-22582 Avoid synchronizing in RuleBasedBreakIterator and ULocale unless strictly necessary
See unicode-org#2775
1 parent 8acebe4 commit f3e50a7

File tree

2 files changed

+53
-54
lines changed

2 files changed

+53
-54
lines changed

icu4j/main/core/src/main/java/com/ibm/icu/text/RuleBasedBreakIterator.java

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
import java.io.OutputStream;
2020
import java.nio.ByteBuffer;
2121
import java.text.CharacterIterator;
22-
import java.util.ArrayList;
23-
import java.util.List;
22+
import java.util.concurrent.ConcurrentLinkedQueue;
2423
import java.util.MissingResourceException;
2524

2625
import com.ibm.icu.impl.CharacterIteration;
@@ -57,9 +56,6 @@ public class RuleBasedBreakIterator extends BreakIterator {
5756
*/
5857
private RuleBasedBreakIterator() {
5958
fDictionaryCharCount = 0;
60-
synchronized(gAllBreakEngines) {
61-
fBreakEngines = new ArrayList<>(gAllBreakEngines);
62-
}
6359
}
6460

6561
/**
@@ -174,9 +170,6 @@ public Object clone() {
174170
if (fText != null) {
175171
result.fText = (CharacterIterator)(fText.clone());
176172
}
177-
synchronized (gAllBreakEngines) {
178-
result.fBreakEngines = new ArrayList<>(gAllBreakEngines);
179-
}
180173
result.fLookAheadMatches = new int[fRData.fFTable.fLookAheadResultsSize];
181174
result.fBreakCache = result.new BreakCache(fBreakCache);
182175
result.fDictionaryCache = result.new DictionaryCache(fDictionaryCache);
@@ -342,23 +335,20 @@ public int hashCode()
342335
* Lazily updated as break engines are needed, because instantiation of
343336
* break engines is expensive.
344337
*
345-
* Because gAllBreakEngines can be referenced concurrently from different
346-
* BreakIterator instances, all access is synchronized.
338+
* Important notes:
339+
* <ul>Because we don't want to add the same LanguageBreakEngine multiple times, all writes
340+
* are synchronized.
341+
* <ul>Read access avoids explicit synchronization, but will end up being synchronized if
342+
* needed.
347343
*/
348-
private static final List<LanguageBreakEngine> gAllBreakEngines;
344+
private static final ConcurrentLinkedQueue<LanguageBreakEngine> gAllBreakEngines;
349345

350346
static {
351347
gUnhandledBreakEngine = new UnhandledBreakEngine();
352-
gAllBreakEngines = new ArrayList<>();
348+
gAllBreakEngines = new ConcurrentLinkedQueue<>();
353349
gAllBreakEngines.add(gUnhandledBreakEngine);
354350
}
355351

356-
/**
357-
* List of all known break engines. Similar to gAllBreakEngines, but local to a
358-
* break iterator, allowing it to be used without synchronization.
359-
*/
360-
private List<LanguageBreakEngine> fBreakEngines;
361-
362352
/**
363353
* Dump the contents of the state table and character classes for this break iterator.
364354
* For debugging only.
@@ -726,19 +716,18 @@ private LanguageBreakEngine getLanguageBreakEngine(int c) {
726716

727717
// We have a dictionary character.
728718
// Does an already instantiated break engine handle it?
729-
for (LanguageBreakEngine candidate : fBreakEngines) {
719+
// First read without synchronization, which could lead to a new language
720+
// break engine being added and we didn't go over it.
721+
for (LanguageBreakEngine candidate : gAllBreakEngines) {
730722
if (candidate.handles(c)) {
731723
return candidate;
732724
}
733725
}
734726

735727
synchronized (gAllBreakEngines) {
736-
// This break iterator's list of break engines didn't handle the character.
737-
// Check the global list, another break iterator may have instantiated the
738-
// desired engine.
728+
// Another break iterator may have instantiated the desired engine.
739729
for (LanguageBreakEngine candidate : gAllBreakEngines) {
740730
if (candidate.handles(c)) {
741-
fBreakEngines.add(candidate);
742731
return candidate;
743732
}
744733
}
@@ -791,7 +780,6 @@ private LanguageBreakEngine getLanguageBreakEngine(int c) {
791780

792781
if (eng != null && eng != gUnhandledBreakEngine) {
793782
gAllBreakEngines.add(eng);
794-
fBreakEngines.add(eng);
795783
}
796784
return eng;
797785
} // end synchronized(gAllBreakEngines)

icu4j/main/core/src/main/java/com/ibm/icu/util/ULocale.java

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -545,13 +545,13 @@ public Locale toLocale() {
545545
/**
546546
* Keep our own default ULocale.
547547
*/
548-
private static Locale defaultLocale = Locale.getDefault();
549-
private static ULocale defaultULocale;
548+
private static volatile ULocale defaultULocale;
550549

551550
private static Locale[] defaultCategoryLocales = new Locale[Category.values().length];
552551
private static ULocale[] defaultCategoryULocales = new ULocale[Category.values().length];
553552

554553
static {
554+
Locale defaultLocale = Locale.getDefault();
555555
defaultULocale = forLocale(defaultLocale);
556556

557557
if (JDKLocaleHelper.hasLocaleCategories()) {
@@ -581,34 +581,47 @@ public Locale toLocale() {
581581
* @stable ICU 2.8
582582
*/
583583
public static ULocale getDefault() {
584+
// Only synchronize if we must update the default locale.
585+
ULocale currentDefaultULocale = defaultULocale;
586+
if (currentDefaultULocale == null) {
587+
// When Java's default locale has extensions (such as ja-JP-u-ca-japanese),
588+
// Locale -> ULocale mapping requires BCP47 keyword mapping data that is currently
589+
// stored in a resource bundle.
590+
// If this happens during the class initialization's call to .forLocale(defaultLocale),
591+
// then defaultULocale is still null until forLocale() returns.
592+
// However, UResourceBundle currently requires non-null default ULocale.
593+
// For now, this implementation returns ULocale.ROOT to avoid the problem.
594+
// TODO: Consider moving BCP47 mapping data out of resource bundle later.
595+
return ULocale.ROOT;
596+
} else if (currentDefaultULocale.locale.equals(Locale.getDefault())) {
597+
return currentDefaultULocale;
598+
}
584599
synchronized (ULocale.class) {
585-
if (defaultULocale == null) {
586-
// When Java's default locale has extensions (such as ja-JP-u-ca-japanese),
587-
// Locale -> ULocale mapping requires BCP47 keyword mapping data that is currently
588-
// stored in a resource bundle. However, UResourceBundle currently requires
589-
// non-null default ULocale. For now, this implementation returns ULocale.ROOT
590-
// to avoid the problem.
600+
Locale currentDefault = Locale.getDefault();
601+
assert currentDefault != null;
591602

592-
// TODO: Consider moving BCP47 mapping data out of resource bundle later.
603+
currentDefaultULocale = defaultULocale;
604+
assert currentDefaultULocale != null;
593605

594-
return ULocale.ROOT;
606+
if (currentDefaultULocale.locale.equals(currentDefault)) {
607+
return currentDefaultULocale;
595608
}
596-
Locale currentDefault = Locale.getDefault();
597-
if (!defaultLocale.equals(currentDefault)) {
598-
defaultLocale = currentDefault;
599-
defaultULocale = forLocale(currentDefault);
600-
601-
if (!JDKLocaleHelper.hasLocaleCategories()) {
602-
// Detected Java default Locale change.
603-
// We need to update category defaults to match
604-
// Java 7's behavior on Android API level 21..23.
605-
for (Category cat : Category.values()) {
606-
int idx = cat.ordinal();
607-
defaultCategoryLocales[idx] = currentDefault;
608-
defaultCategoryULocales[idx] = forLocale(currentDefault);
609-
}
610-
} }
611-
return defaultULocale;
609+
610+
ULocale nextULocale = forLocale(currentDefault);
611+
assert nextULocale != null;
612+
613+
if (!JDKLocaleHelper.hasLocaleCategories()) {
614+
// Detected Java default Locale change.
615+
// We need to update category defaults to match
616+
// Java 7's behavior on Android API level 21..23.
617+
for (Category cat : Category.values()) {
618+
int idx = cat.ordinal();
619+
defaultCategoryLocales[idx] = currentDefault;
620+
defaultCategoryULocales[idx] = nextULocale;
621+
}
622+
}
623+
624+
return defaultULocale = nextULocale;
612625
}
613626
}
614627

@@ -630,8 +643,7 @@ public static ULocale getDefault() {
630643
* @stable ICU 3.0
631644
*/
632645
public static synchronized void setDefault(ULocale newLocale){
633-
defaultLocale = newLocale.toLocale();
634-
Locale.setDefault(defaultLocale);
646+
Locale.setDefault(newLocale.toLocale());
635647
defaultULocale = newLocale;
636648
// This method also updates all category default locales
637649
for (Category cat : Category.values()) {
@@ -675,8 +687,7 @@ public static ULocale getDefault(Category category) {
675687
// time.
676688

677689
Locale currentDefault = Locale.getDefault();
678-
if (!defaultLocale.equals(currentDefault)) {
679-
defaultLocale = currentDefault;
690+
if (!defaultULocale.locale.equals(currentDefault)) {
680691
defaultULocale = forLocale(currentDefault);
681692

682693
for (Category cat : Category.values()) {

0 commit comments

Comments
 (0)