27
27
import java .lang .ref .PhantomReference ;
28
28
import java .lang .ref .Reference ;
29
29
import java .lang .ref .ReferenceQueue ;
30
- import java .util .Iterator ;
31
- import java .util .Map ;
30
+ import java .util .*;
32
31
import java .util .concurrent .ConcurrentHashMap ;
33
32
import java .util .concurrent .atomic .AtomicBoolean ;
34
33
import java .util .concurrent .atomic .AtomicLong ;
@@ -60,10 +59,11 @@ public class Cleaner {
60
59
* cleared, the ThreadLocal instance is lost, and so are the pending
61
60
* objects.
62
61
*
63
- * The Master Cleaner handles the first issue by regularly handling the
64
- * queues of the Cleaners registered with it.
65
- * The seconds issue is handled by registering the per-thread Cleaner
66
- * instances with the Master's reference queue.
62
+ * The Master Cleaner handles the first issue by regularly checking the
63
+ * activity of the Cleaners registered with it, and taking over the queues
64
+ * of any cleaners appearing to be idle.
65
+ * Similarly, the second issue is handled by taking over the queues of threads
66
+ * that have terminated.
67
67
*/
68
68
69
69
public static final long MASTER_CLEANUP_INTERVAL_MS = 5000 ;
@@ -116,28 +116,28 @@ public static synchronized void add(Cleaner cleaner) {
116
116
if (INSTANCE == null ) {
117
117
INSTANCE = new MasterCleaner ();
118
118
}
119
- final CleanerImpl impl = cleaner .impl ;
120
- INSTANCE .cleanerImpls .put (impl , true );
121
- INSTANCE .register (cleaner , () -> INSTANCE .cleanerImpls .put (impl , false ));
119
+ INSTANCE .cleaners .add (cleaner );
122
120
}
123
121
122
+ /** @return true if the caller thread can terminate */
124
123
private static synchronized boolean deleteIfEmpty (MasterCleaner caller ) {
125
- if (INSTANCE == caller && INSTANCE .cleanerImpls .isEmpty ()) {
124
+ if (INSTANCE == caller && INSTANCE .cleaners .isEmpty ()) {
126
125
INSTANCE = null ;
127
126
}
128
- return caller .cleanerImpls .isEmpty ();
127
+ return caller .cleaners .isEmpty ();
129
128
}
130
129
131
- final Map <CleanerImpl ,Boolean > cleanerImpls = new ConcurrentHashMap <CleanerImpl ,Boolean >();
132
- private long lastNonEmpty = System .currentTimeMillis ();
130
+ final Set <Cleaner > cleaners = Collections .synchronizedSet (new HashSet <>());
131
+ final Set <CleanerImpl > referencedCleaners = new HashSet <>();
132
+ final Set <CleanerImpl > watchedCleaners = new HashSet <>();
133
133
134
134
private MasterCleaner () {
135
- super (true );
136
135
Thread cleanerThread = new Thread (() -> {
136
+ long lastNonEmpty = System .currentTimeMillis ();
137
137
long now ;
138
138
long lastMasterRun = 0 ;
139
139
while ((now = System .currentTimeMillis ()) < lastNonEmpty + MASTER_MAX_LINGER_MS || !deleteIfEmpty (MasterCleaner .this )) {
140
- if (!cleanerImpls .isEmpty ()) { lastNonEmpty = now ; }
140
+ if (!cleaners .isEmpty ()) { lastNonEmpty = now ; }
141
141
try {
142
142
Reference <?> ref = impl .referenceQueue .remove (MASTER_CLEANUP_INTERVAL_MS );
143
143
if (ref instanceof CleanerRef ) {
@@ -164,33 +164,59 @@ private MasterCleaner() {
164
164
}
165
165
166
166
private void masterCleanup () {
167
- Iterator <Map .Entry <CleanerImpl ,Boolean >> it = cleanerImpls .entrySet ().iterator ();
168
- while (it .hasNext ()) {
169
- Map .Entry <CleanerImpl ,Boolean > entry = it .next ();
170
- entry .getKey ().cleanQueue ();
171
- if (!entry .getValue () && entry .getKey ().cleanables .isEmpty ()) {
167
+ for (Iterator <Map .Entry <Thread ,Cleaner >> it = Cleaner .INSTANCES .entrySet ().iterator (); it .hasNext (); ) {
168
+ Map .Entry <Thread ,Cleaner > entry = it .next ();
169
+ if (!cleaners .contains (entry .getValue ())) { continue ; }
170
+ Cleaner cleaner = entry .getValue ();
171
+ long currentCount = cleaner .counter .get ();
172
+ if (currentCount == cleaner .lastCount // no new cleanables registered since last master cleanup interval -> assume it is no longer in use
173
+ || !entry .getKey ().isAlive ()) { // owning thread died -> assume it is no longer in use
172
174
it .remove ();
175
+ CleanerImpl impl = cleaner .impl ;
176
+ referencedCleaners .add (impl );
177
+ watchedCleaners .add (impl );
178
+ register (cleaner , () -> referencedCleaners .remove (impl ));
179
+ cleaners .remove (cleaner );
180
+ } else {
181
+ cleaner .lastCount = currentCount ;
182
+ }
183
+ }
184
+
185
+ for (Iterator <CleanerImpl > it = watchedCleaners .iterator (); it .hasNext (); ) {
186
+ CleanerImpl impl = it .next ();
187
+ impl .cleanQueue ();
188
+ if (!referencedCleaners .contains (impl )) {
189
+ if (impl .cleanables .isEmpty ()) { it .remove (); }
173
190
}
174
191
}
175
192
}
176
193
}
177
194
178
- private static final ThreadLocal < Cleaner > MY_INSTANCE = ThreadLocal . withInitial (() -> new Cleaner ( false ) );
195
+ private static final Map < Thread , Cleaner > INSTANCES = new ConcurrentHashMap <>( );
179
196
180
197
public static Cleaner getCleaner () {
181
- return MY_INSTANCE . get ( );
198
+ return INSTANCES . computeIfAbsent ( Thread . currentThread (), Cleaner :: new );
182
199
}
183
200
184
201
protected final CleanerImpl impl ;
202
+ protected final Thread owner ;
203
+ protected final AtomicLong counter = new AtomicLong (Long .MIN_VALUE );
204
+ protected long lastCount ; // used by MasterCleaner only
205
+
206
+ private Cleaner () {
207
+ this (null );
208
+ }
185
209
186
- private Cleaner (boolean master ) {
210
+ private Cleaner (Thread owner ) {
187
211
impl = new CleanerImpl ();
188
- if (!master ) {
212
+ this .owner = owner ;
213
+ if (owner != null ) {
189
214
MasterCleaner .add (this );
190
215
}
191
216
}
192
217
193
218
public Cleanable register (Object obj , Runnable cleanupTask ) {
219
+ counter .incrementAndGet ();
194
220
return impl .register (obj , cleanupTask );
195
221
}
196
222
0 commit comments