Skip to content

Commit ef90455

Browse files
authored
Merge pull request #494 from AltBeacon/fix-singleton-creation-thread-safety
Ensure singleton object creation thread safety
2 parents 313262d + 0154f46 commit ef90455

File tree

5 files changed

+74
-23
lines changed

5 files changed

+74
-23
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Bug Fixes:
88
- Fix bug causing brief scan dropouts after starting a scan after a long period
99
of inactivity (i.e. startup and background-foreground transitions) due to
1010
Android N scan limits (#489, Aaron Kromer)
11+
- Ensure thread safety for singleton creation of `BeaconManager`,
12+
`DetectionTracker`, `MonitoringStatus`, and `Stats`. (#494, Aaron Kromer)
1113

1214

1315
### 2.9.2 / 2016-11-22

src/main/java/org/altbeacon/beacon/BeaconManager.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
public class BeaconManager {
109109
private static final String TAG = "BeaconManager";
110110
private Context mContext;
111-
protected static BeaconManager client = null;
111+
protected static volatile BeaconManager client = null;
112112
private final ConcurrentMap<BeaconConsumer, ConsumerInfo> consumers = new ConcurrentHashMap<BeaconConsumer,ConsumerInfo>();
113113
private Messenger serviceMessenger = null;
114114
protected final Set<RangeNotifier> rangeNotifiers = new CopyOnWriteArraySet<>();
@@ -123,6 +123,11 @@ public class BeaconManager {
123123
private static boolean sAndroidLScanningDisabled = false;
124124
private static boolean sManifestCheckingDisabled = false;
125125

126+
/**
127+
* Private lock object for singleton initialization protecting against denial-of-service attack.
128+
*/
129+
private static final Object SINGLETON_LOCK = new Object();
130+
126131
/**
127132
* Set to true if you want to show library debugging.
128133
*
@@ -239,11 +244,29 @@ public static long getRegionExitPeriod(){
239244
* or non-Service class, you can attach it to another singleton or a subclass of the Android Application class.
240245
*/
241246
public static BeaconManager getInstanceForApplication(Context context) {
242-
if (client == null) {
243-
LogManager.d(TAG, "BeaconManager instance creation");
244-
client = new BeaconManager(context);
247+
/*
248+
* Follow double check pattern from Effective Java v2 Item 71.
249+
*
250+
* Bloch recommends using the local variable for this for performance reasons:
251+
*
252+
* > What this variable does is ensure that `field` is read only once in the common case
253+
* > where it's already initialized. While not strictly necessary, this may improve
254+
* > performance and is more elegant by the standards applied to low-level concurrent
255+
* > programming. On my machine, [this] is about 25 percent faster than the obvious
256+
* > version without a local variable.
257+
*
258+
* Joshua Bloch. Effective Java, Second Edition. Addison-Wesley, 2008. pages 283-284
259+
*/
260+
BeaconManager instance = client;
261+
if (instance == null) {
262+
synchronized (SINGLETON_LOCK) {
263+
instance = client;
264+
if (instance == null) {
265+
client = instance = new BeaconManager(context);
266+
}
267+
}
245268
}
246-
return client;
269+
return instance;
247270
}
248271

249272
protected BeaconManager(Context context) {

src/main/java/org/altbeacon/beacon/service/DetectionTracker.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@
66
* Created by dyoung on 1/10/15.
77
*/
88
public class DetectionTracker {
9-
private static DetectionTracker sDetectionTracker = null;
9+
private static final DetectionTracker INSTANCE = new DetectionTracker();
10+
1011
private long mLastDetectionTime = 0l;
1112
private DetectionTracker() {
1213

1314
}
14-
public static synchronized DetectionTracker getInstance() {
15-
if (sDetectionTracker == null) {
16-
sDetectionTracker = new DetectionTracker();
17-
}
18-
return sDetectionTracker;
15+
public static DetectionTracker getInstance() {
16+
return INSTANCE;
1917
}
2018
public long getLastDetectionTime() {
2119
return mLastDetectionTime;

src/main/java/org/altbeacon/beacon/service/MonitoringStatus.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import static android.content.Context.MODE_PRIVATE;
2424

2525
public class MonitoringStatus {
26-
private static MonitoringStatus sInstance;
26+
private static volatile MonitoringStatus sInstance;
2727
private static final int MAX_REGIONS_FOR_STATUS_PRESERVATION = 50;
2828
private static final int MAX_STATUS_PRESERVATION_FILE_AGE_TO_RESTORE_SECS = 60 * 15;
2929
private static final String TAG = MonitoringStatus.class.getSimpleName();
@@ -35,15 +35,35 @@ public class MonitoringStatus {
3535

3636
private boolean mStatePreservationIsOn = true;
3737

38+
/**
39+
* Private lock object for singleton initialization protecting against denial-of-service attack.
40+
*/
41+
private static final Object SINGLETON_LOCK = new Object();
42+
3843
public static MonitoringStatus getInstanceForApplication(Context context) {
39-
if (sInstance == null) {
40-
synchronized (MonitoringStatus.class) {
41-
if (sInstance == null) {
42-
sInstance = new MonitoringStatus(context.getApplicationContext());
44+
/*
45+
* Follow double check pattern from Effective Java v2 Item 71.
46+
*
47+
* Bloch recommends using the local variable for this for performance reasons:
48+
*
49+
* > What this variable does is ensure that `field` is read only once in the common case
50+
* > where it's already initialized. While not strictly necessary, this may improve
51+
* > performance and is more elegant by the standards applied to low-level concurrent
52+
* > programming. On my machine, [this] is about 25 percent faster than the obvious
53+
* > version without a local variable.
54+
*
55+
* Joshua Bloch. Effective Java, Second Edition. Addison-Wesley, 2008. pages 283-284
56+
*/
57+
MonitoringStatus instance = sInstance;
58+
if (instance == null) {
59+
synchronized (SINGLETON_LOCK) {
60+
instance = sInstance;
61+
if (instance == null) {
62+
sInstance = instance = new MonitoringStatus(context.getApplicationContext());
4363
}
4464
}
4565
}
46-
return sInstance;
66+
return instance;
4767
}
4868

4969
public MonitoringStatus(Context context) {

src/main/java/org/altbeacon/beacon/service/Stats.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
* Created by dyoung on 10/16/14.
1313
*/
1414
public class Stats {
15+
private static final Stats INSTANCE = new Stats();
1516
private static final String TAG = "Stats";
17+
18+
/**
19+
* Synchronize all usage as this is not a thread safe class.
20+
*/
1621
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS");
1722

1823
private ArrayList<Sample> mSamples;
@@ -21,14 +26,11 @@ public class Stats {
2126
private boolean mEnableHistoricalLogging;
2227
private boolean mEnabled;
2328
private Sample mSample;
24-
private static Stats mInstance;
2529

2630
public static Stats getInstance() {
27-
if(mInstance == null) {
28-
mInstance = new Stats();
29-
}
30-
return mInstance;
31+
return INSTANCE;
3132
}
33+
3234
private Stats() {
3335
mSampleIntervalMillis = 0l;
3436
clearSamples();
@@ -105,7 +107,13 @@ private void logSample(Sample sample, boolean showHeader) {
105107
}
106108

107109
private String formattedDate(Date d) {
108-
return d == null ? "" : SIMPLE_DATE_FORMAT.format(d);
110+
String formattedDate = "";
111+
if (d != null) {
112+
synchronized (SIMPLE_DATE_FORMAT) {
113+
formattedDate = SIMPLE_DATE_FORMAT.format(d);
114+
}
115+
}
116+
return formattedDate;
109117
}
110118

111119
private void logSamples() {

0 commit comments

Comments
 (0)