Index: working_vm/vm/vmcore/src/kernel_classes/javasrc/java/lang/Thread.java =================================================================== --- working_vm/vm/vmcore/src/kernel_classes/javasrc/java/lang/Thread.java (revision 660156) +++ working_vm/vm/vmcore/src/kernel_classes/javasrc/java/lang/Thread.java (working copy) @@ -103,12 +103,6 @@ */ private Runnable target; - /** - * This map is used to provide ThreadLocal functionality. - * Maps ThreadLocal object to value. Lazy initialization is - * used to avoid circular dependance. - */ - private Map, Object> localValues = null; /** * Uncaught exception handler for this thread @@ -160,6 +154,9 @@ * references. */ private static final int GC_WATERMARK_MAX_COUNT = 700; + + ThreadLocal.Values localValues; + ThreadLocal.Values inheritableValues; /** * @com.intel.drl.spec_ref @@ -251,6 +248,14 @@ SecurityUtils.putContext(this, AccessController.getContext()); // adding the thread to the thread group should be the last action group.add(this); + + // TODO: Crashes VM +/* + if (currentThread().inheritableValues != null) { + inheritableValues + = new ThreadLocal.Values(currentThread().inheritableValues); + } +*/ } /** @@ -288,7 +293,7 @@ this.name = (name != THREAD) ? this.name = name.toString() : THREAD + threadId; - initializeInheritableLocalValues(currentThread); + checkGCWatermark(); @@ -304,6 +309,12 @@ SecurityUtils.putContext(this, AccessController.getContext()); checkAccess(); +/* + if (currentThread().inheritableValues != null) { + inheritableValues + = new ThreadLocal.Values(currentThread().inheritableValues); + } +*/ } /** @@ -933,86 +944,6 @@ } /** - * Associates the value specified to the ThreadLocal object - * given.
- * This nethod is designed to provide ThreadLocal - * functionality. - */ - void setThreadLocal(ThreadLocal local, Object value) { - if (localValues == null) { - localValues = new IdentityHashMap, Object>(); - } - localValues.put(local, value); - } - - /** - * Returns the value associated with the ThreadLocal object - * specified. If no value is associated, returns the value produced by - * initialValue() method called for this object and - * associates this value to ThreadLocal object.
- * This nethod is designed to provide ThreadLocal - * functionality. - */ - Object getThreadLocal(ThreadLocal local) { - Object value; - if (localValues == null) { - localValues = new IdentityHashMap, Object>(); - value = local.initialValue(); - localValues.put(local, value); - return value; - } - value = localValues.get(local); - if (value != null) { - return value; - } else { - if (localValues.containsKey(local)) { - return null; - } else { - value = local.initialValue(); - localValues.put(local, value); - return value; - } - } - } - - /** - * Removes the association (if any) between the ThreadLocal object - * given and this thread's value.
- * This nethod is designed to provide ThreadLocal - * functionality. - */ - void removeLocalValue(ThreadLocal local) { - if (localValues != null) { - localValues.remove(local); - } - } - - /** - * Initializes local values represented by - * InheritableThreadLocal objects having local values for the - * parent thread
- * This method is designed to provide the functionality of - * InheritableThreadLocal class
- * This method should be called from Thread's constructor. - */ - private void initializeInheritableLocalValues(Thread parent) - { - Map, Object> parentLocalValues = parent.localValues; - if (parentLocalValues == null) { - return; - } - localValues = new IdentityHashMap, Object>(parentLocalValues.size()); - for (Iterator> it = parentLocalValues.keySet().iterator(); it.hasNext();) { - ThreadLocal local = it.next(); - if (local instanceof InheritableThreadLocal) { - Object parentValue = parentLocalValues.get(local); - InheritableThreadLocal iLocal = (InheritableThreadLocal) local; - localValues.put(local, iLocal.childValue(parentValue)); - } - } - } - - /** * generates a unique thread ID */ private static synchronized long getNextThreadId() Index: working_classlib/modules/luni-kernel/src/main/java/java/lang/Thread.java =================================================================== --- working_classlib/modules/luni-kernel/src/main/java/java/lang/Thread.java (revision 660154) +++ working_classlib/modules/luni-kernel/src/main/java/java/lang/Thread.java (working copy) @@ -37,6 +37,10 @@ */ public class Thread implements Runnable { + ThreadLocal.Values localValues; + ThreadLocal.Values inheritableValues; + + /** * A representation of a thread's state. A given thread may only be in one * state at a time. Index: working_classlib/modules/luni/src/main/java/java/lang/ThreadLocal.java =================================================================== --- working_classlib/modules/luni/src/main/java/java/lang/ThreadLocal.java (revision 660154) +++ working_classlib/modules/luni/src/main/java/java/lang/ThreadLocal.java (working copy) @@ -1,74 +1,523 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * Copyright (C) 2008 Google Inc. * - * http://www.apache.org/licenses/LICENSE-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package java.lang; +import java.lang.ref.WeakReference; +import java.lang.ref.Reference; +import java.util.concurrent.atomic.AtomicInteger; + /** - * A ThreadLocal is a variable that has a per-thread value. Different Threads - * may reference the same ThreadLocal object, but the values they observe will - * not be ==. This provides Thread local storage. + * A variable for which each thread has its own value. Supports {@code null} + * values. * * @see java.lang.Thread + * @author Bob Lee */ public class ThreadLocal { + + /* Thanks to Josh Bloch and Doug Lea for code reviews and impl advice. */ + /** - * Constructs a new ThreadLocal object + * Creates a new thread local variable. */ - public ThreadLocal() { - super(); - } + public ThreadLocal() {} /** - * Return the value of this variable under - * Thread.currentThread() + * Returns the value of this variable for the current thread. If an entry + * doesn't yet exist for this variable on this thread, this method will + * create an entry, populating the value with the result of + * {@link #initialValue()}. */ @SuppressWarnings("unchecked") public T get() { - return (T) Thread.currentThread().getThreadLocal(this); + // Optimized for the fast path. + Thread currentThread = Thread.currentThread(); + Values values = values(currentThread); + if (values != null) { + Object[] table = values.table; + int index = hash & values.mask; + if (this.reference == table[index]) { + return (T) table[index + 1]; + } + } else { + values = initializeValues(currentThread); + } + + return (T) values.getAfterMiss(this); } /** - * Return the initial value of this variable under - * Thread.currentThread() + * Provides the initial value of this variable for the current thread. + * The default implementation returns {@code null}. */ protected T initialValue() { return null; } /** - * Set the value of this variable under Thread.currentThread() + * Sets the value of this variable for the current thread. If set to + * null, the value will be set to null and the underlying entry will still + * be present. */ public void set(T value) { - Thread.currentThread().setThreadLocal(this, value); + Thread currentThread = Thread.currentThread(); + Values values = values(currentThread); + if (values == null) { + values = initializeValues(currentThread); + } + values.put(this, value); } /** - *

- * Removes the current value, such that the next call to {@link #get()} will - * return the default value, as defined by {@link #initialValue()}. - *

- * - * @since 1.5 + * Removes the entry for this variable in the current thread. If this call + * is followed by a {@link #get()} before a {@link #set(Object)}, + * {@code #get()} will call {@link #initialValue()} and create a new + * entry with the resulting value. */ public void remove() { - /* - * TODO Consider adding an explicit removeThreadLocal method to Thread - * for VM implementations to take advantage of extra possible space. + Thread currentThread = Thread.currentThread(); + Values values = values(currentThread); + if (values != null) { + values.remove(this); + } + } + + /** + * Creates Values instance for this thread and variable type. + */ + Values initializeValues(Thread current) { + return current.localValues = new Values(); + } + + /** + * Gets Values instance for this thread and variable type. + */ + Values values(Thread current) { + return current.localValues; + } + + /** Weak reference to this thread local instance. */ + private final Reference> reference + = new WeakReference>(this); + + /** Hash counter. */ + private static AtomicInteger hashCounter = new AtomicInteger(0); + + /** + * Internal hash. We deliberately don't bother with #hashCode(). + * Hashes must be even. This ensures that the result of + * (hash & (table.length - 1)) points to a key and not a value. + * + * We increment by Doug Lea's Magic Number(TM) (*2 since keys are in + * every other bucket) to help prevent clustering. + */ + private final int hash = hashCounter.getAndAdd(0x61c88647 << 1); + + /** + * Per-thread map of ThreadLocal instances to values. + */ + static class Values { + + /** + * Size must always be a power of 2. */ - Thread.currentThread().setThreadLocal(this, initialValue()); + private static final int INITIAL_SIZE = 16; + + /** + * Placeholder for deleted entries. + */ + private static final Object TOMBSTONE = new Object(); + + /** + * Map entries. Contains alternating keys (ThreadLocal) and values. + * The length is always a power of 2. + */ + private Object[] table; + + /** Used to turn hashes into indices. */ + private int mask; + + /** Number of live entries. */ + private int size; + + /** Number of tombstones. */ + private int tombstones; + + /** Maximum number of live entries and tombstones. */ + private int maximumLoad; + + /** Points to the next cell to clean up. */ + private int clean; + + /** + * Constructs a new, empty instance. + */ + Values() { + initializeTable(INITIAL_SIZE); + this.size = 0; + this.tombstones = 0; + } + + /** + * Used for InheritableThreadLocals. + */ + Values(Values fromParent) { + this.table = fromParent.table.clone(); + this.mask = fromParent.mask; + this.size = fromParent.size; + this.tombstones = fromParent.tombstones; + this.maximumLoad = fromParent.maximumLoad; + this.clean = fromParent.clean; + inheritValues(fromParent); + } + + /** + * Inherits values from a parent thread. + */ + @SuppressWarnings({"unchecked"}) + private void inheritValues(Values fromParent) { + // Transfer values from parent to child thread. + Object[] table = this.table; + for (int i = table.length - 2; i >= 0; i -= 2) { + Object k = table[i]; + + if (k == null || k == TOMBSTONE) { + // Skip this entry. + continue; + } + + // The table can only contain null, tombstones and references. + Reference> reference + = (Reference>) k; + // Raw type enables us to pass in an Object below. + InheritableThreadLocal key = reference.get(); + if (key != null) { + // Replace value with filtered value. + // We should just let exceptions bubble out and tank + // the thread creation + table[i + 1] = key.childValue(fromParent.table[i + 1]); + } else { + // The key was reclaimed. + table[i] = TOMBSTONE; + table[i + 1] = null; + fromParent.table[i] = TOMBSTONE; + fromParent.table[i + 1] = null; + + tombstones++; + fromParent.tombstones++; + + size--; + fromParent.size--; + } + } + } + + /** + * Creates a new, empty table with the given capacity. + */ + private void initializeTable(int capacity) { + this.table = new Object[capacity << 1]; + this.mask = table.length - 1; + this.clean = 0; + this.maximumLoad = capacity * 2 / 3; // 2/3 + } + + /** + * Cleans up after garbage-collected thread locals. + */ + private void cleanUp() { + if (rehash()) { + // If we rehashed, we needn't clean up (clean up happens as + // a side effect). + return; + } + + if (size == 0) { + // No live entries == nothing to clean. + return; + } + + // Clean log(table.length) entries picking up where we left off + // last time. + int index = clean; + Object[] table = this.table; + for (int counter = table.length; counter > 0; counter >>= 1, + index = next(index)) { + Object k = table[index]; + + if (k == TOMBSTONE || k == null) { + continue; // on to next entry + } + + // The table can only contain null, tombstones and references. + @SuppressWarnings("unchecked") + Reference> reference + = (Reference>) k; + if (reference.get() == null) { + // This thread local was reclaimed by the garbage collector. + table[index] = TOMBSTONE; + table[index + 1] = null; + tombstones++; + size--; + } + } + + // Point cursor to next index. + clean = index; + } + + /** + * Rehashes the table, expanding or contracting it as necessary. + * Gets rid of tombstones. Returns true if a rehash occurred. + * We must rehash every time we fill a null slot; we depend on the + * presence of null slots to end searches (otherwise, we'll infinitely + * loop). + */ + private boolean rehash() { + if (tombstones + size < maximumLoad) { + return false; + } + + int capacity = table.length >> 1; + + // Default to the same capacity. This will create a table of the + // same size and move over the live entries, analogous to a + // garbage collection. This should only happen if you churn a + // bunch of thread local garbage (removing and reinserting + // the same thread locals over and over will overwrite tombstones + // and not fill up the table). + int newCapacity = capacity; + + if (size > (capacity >> 1)) { + // More than 1/2 filled w/ live entries. + // Double size. + newCapacity = capacity << 1; + } + + Object[] oldTable = this.table; + + // Allocate new table. + initializeTable(newCapacity); + + // We won't have any tombstones after this. + this.tombstones = 0; + + // If we have no live entries, we can quit here. + if (size == 0) { + return true; + } + + // Move over entries. + for (int i = oldTable.length - 2; i >= 0; i -= 2) { + Object k = oldTable[i]; + if (k == null || k == TOMBSTONE) { + // Skip this entry. + continue; + } + + // The table can only contain null, tombstones and references. + @SuppressWarnings("unchecked") + Reference> reference + = (Reference>) k; + ThreadLocal key = reference.get(); + if (key != null) { + // Entry is still live. Move it over. + add(key, oldTable[i + 1]); + } else { + // The key was reclaimed. + size--; + } + } + + return true; + } + + /** + * Adds an entry during rehashing. Compared to put(), this method + * doesn't have to clean up, check for existing entries, account for + * tombstones, etc. + */ + void add(ThreadLocal key, Object value) { + for (int index = key.hash & mask;; index = next(index)) { + Object k = table[index]; + if (k == null) { + table[index] = key.reference; + table[index + 1] = value; + return; + } + } + } + + /** + * Sets entry for given ThreadLocal to given value, creating an + * entry if necessary. + */ + void put(ThreadLocal key, Object value) { + cleanUp(); + + // Keep track of first tombstone. That's where we want to go back + // and add an entry if necessary. + int firstTombstone = -1; + + for (int index = key.hash & mask;; index = next(index)) { + Object k = table[index]; + + if (k == key.reference) { + // Replace existing entry. + table[index + 1] = value; + return; + } + + if (k == null) { + if (firstTombstone == -1) { + // Fill in null slot. + table[index] = key.reference; + table[index + 1] = value; + size++; + return; + } + + // Go back and replace first tombstone. + table[firstTombstone] = key.reference; + table[firstTombstone + 1] = value; + tombstones--; + size++; + return; + } + + // Remember first tombstone. + if (firstTombstone == -1 && k == TOMBSTONE) { + firstTombstone = index; + } + } + } + + /** + * Gets value for given ThreadLocal after not finding it in the first + * slot. + */ + Object getAfterMiss(ThreadLocal key) { + Object[] table = this.table; + int index = key.hash & mask; + + // If the first slot is empty, the search is over. + if (table[index] == null) { + Object value = key.initialValue(); + + // If the table is still the same and the slot is still empty... + if (this.table == table && table[index] == null) { + table[index] = key.reference; + table[index + 1] = value; + size++; + + cleanUp(); + return value; + } + + // The table changed during initialValue(). + put(key, value); + return value; + } + + // Keep track of first tombstone. That's where we want to go back + // and add an entry if necessary. + int firstTombstone = -1; + + // Continue search. + for (index = next(index);; index = next(index)) { + Object reference = table[index]; + if (reference == key.reference) { + return table[index + 1]; + } + + // If no entry was found... + if (reference == null) { + Object value = key.initialValue(); + + // If the table is still the same... + if (this.table == table) { + // If we passed a tombstone and that slot still + // contains a tombstone... + if (firstTombstone > -1 + && table[firstTombstone] == TOMBSTONE) { + table[firstTombstone] = key.reference; + table[firstTombstone + 1] = value; + tombstones--; + size++; + + // No need to clean up here. We aren't filling + // in a null slot. + return value; + } + + // If this slot is still empty... + if (table[index] == null) { + table[index] = key.reference; + table[index + 1] = value; + size++; + + cleanUp(); + return value; + } + } + + // The table changed during initialValue(). + put(key, value); + return value; + } + + if (firstTombstone == -1 && reference == TOMBSTONE) { + // Keep track of this tombstone so we can overwrite it. + firstTombstone = index; + } + } + } + + /** + * Removes entry for the given ThreadLocal. + */ + void remove(ThreadLocal key) { + cleanUp(); + + for (int index = key.hash & mask;; index = next(index)) { + Object reference = table[index]; + + if (reference == key.reference) { + // Success! + table[index] = TOMBSTONE; + table[index + 1] = null; + tombstones++; + size--; + return; + } + + if (reference == null) { + // No entry found. + return; + } + } + } + + /** + * Gets the next index. If we're at the end of the table, we wrap back + * around to 0. + */ + private int next(int index) { + return (index + 2) & mask; + } } }