2727import java .lang .ref .PhantomReference ;
2828import java .lang .ref .Reference ;
2929import java .lang .ref .ReferenceQueue ;
30- import java .util .Iterator ;
31- import java .util .Map ;
30+ import java .util .*;
3231import java .util .concurrent .ConcurrentHashMap ;
3332import java .util .concurrent .atomic .AtomicBoolean ;
3433import java .util .concurrent .atomic .AtomicLong ;
@@ -60,10 +59,11 @@ public class Cleaner {
6059 * cleared, the ThreadLocal instance is lost, and so are the pending
6160 * objects.
6261 *
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.
6767 */
6868
6969 public static final long MASTER_CLEANUP_INTERVAL_MS = 5000 ;
@@ -116,28 +116,28 @@ public static synchronized void add(Cleaner cleaner) {
116116 if (INSTANCE == null ) {
117117 INSTANCE = new MasterCleaner ();
118118 }
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 );
122120 }
123121
122+ /** @return true if the caller thread can terminate */
124123 private static synchronized boolean deleteIfEmpty (MasterCleaner caller ) {
125- if (INSTANCE == caller && INSTANCE .cleanerImpls .isEmpty ()) {
124+ if (INSTANCE == caller && INSTANCE .cleaners .isEmpty ()) {
126125 INSTANCE = null ;
127126 }
128- return caller .cleanerImpls .isEmpty ();
127+ return caller .cleaners .isEmpty ();
129128 }
130129
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 <>();
133133
134134 private MasterCleaner () {
135- super (true );
136135 Thread cleanerThread = new Thread (() -> {
136+ long lastNonEmpty = System .currentTimeMillis ();
137137 long now ;
138138 long lastMasterRun = 0 ;
139139 while ((now = System .currentTimeMillis ()) < lastNonEmpty + MASTER_MAX_LINGER_MS || !deleteIfEmpty (MasterCleaner .this )) {
140- if (!cleanerImpls .isEmpty ()) { lastNonEmpty = now ; }
140+ if (!cleaners .isEmpty ()) { lastNonEmpty = now ; }
141141 try {
142142 Reference <?> ref = impl .referenceQueue .remove (MASTER_CLEANUP_INTERVAL_MS );
143143 if (ref instanceof CleanerRef ) {
@@ -164,33 +164,59 @@ private MasterCleaner() {
164164 }
165165
166166 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
172174 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 (); }
173190 }
174191 }
175192 }
176193 }
177194
178- private static final ThreadLocal < Cleaner > MY_INSTANCE = ThreadLocal . withInitial (() -> new Cleaner ( false ) );
195+ private static final Map < Thread , Cleaner > INSTANCES = new ConcurrentHashMap <>( );
179196
180197 public static Cleaner getCleaner () {
181- return MY_INSTANCE . get ( );
198+ return INSTANCES . computeIfAbsent ( Thread . currentThread (), Cleaner :: new );
182199 }
183200
184201 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+ }
185209
186- private Cleaner (boolean master ) {
210+ private Cleaner (Thread owner ) {
187211 impl = new CleanerImpl ();
188- if (!master ) {
212+ this .owner = owner ;
213+ if (owner != null ) {
189214 MasterCleaner .add (this );
190215 }
191216 }
192217
193218 public Cleanable register (Object obj , Runnable cleanupTask ) {
219+ counter .incrementAndGet ();
194220 return impl .register (obj , cleanupTask );
195221 }
196222
0 commit comments