LOG4J2-1349 GC-free ThreadContext initial commit

- move ArrayContextData and OpenHashMapContextData to spi package in log4j-api 
module
- move BiConsumer/TriConsumer to util package in log4j-api module
- added CopyOnWriteSortedArrayThreadContext and SortedArrayThreadContext to 
support garbage-free and classic thread context strategies


Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo
Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/1118b27f
Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/1118b27f
Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/1118b27f

Branch: refs/heads/LOG4J2-1349-gcfree-threadcontext
Commit: 1118b27fafe76c4d6e36cd71ffa2eee90991710f
Parents: 88d2066
Author: rpopma <[email protected]>
Authored: Fri Aug 19 09:10:15 2016 +0900
Committer: rpopma <[email protected]>
Committed: Tue Aug 23 00:31:03 2016 +0900

----------------------------------------------------------------------
 .../logging/log4j/spi/ArrayContextData.java     | 448 ++++++++++
 .../apache/logging/log4j/spi/ContextData.java   | 115 +++
 .../CopyOnWriteSortedArrayThreadContext.java    | 156 ++++
 .../logging/log4j/spi/MutableContextData.java   |  68 ++
 .../log4j/spi/OpenHashMapContextData.java       | 883 ++++++++++++++++++
 .../log4j/spi/SortedArrayThreadContext.java     | 156 ++++
 .../apache/logging/log4j/util/BiConsumer.java   |  19 +
 .../apache/logging/log4j/util/TriConsumer.java  |  21 +
 .../logging/log4j/spi/ArrayContextDataTest.java | 569 ++++++++++++
 .../log4j/spi/OpenHashMapContextDataTest.java   | 507 +++++++++++
 .../logging/log4j/core/AbstractLogEvent.java    |   1 +
 .../apache/logging/log4j/core/ContextData.java  | 119 ---
 .../org/apache/logging/log4j/core/LogEvent.java |   1 +
 .../db/jpa/AbstractLogEventWrapperEntity.java   |   2 +-
 .../ContextDataAttributeConverter.java          |   2 +-
 .../ContextDataJsonAttributeConverter.java      |   6 +-
 .../logging/log4j/core/async/AsyncLogger.java   |   2 +-
 .../log4j/core/async/RingBufferLogEvent.java    |   4 +-
 .../async/RingBufferLogEventTranslator.java     |   2 +-
 .../log4j/core/impl/ArrayContextData.java       | 438 ---------
 .../log4j/core/impl/ContextDataFactory.java     |   3 +
 .../log4j/core/impl/ContextDataInjector.java    |   3 +
 .../core/impl/ContextDataInjectorFactory.java   |   2 +
 .../logging/log4j/core/impl/Log4jLogEvent.java  |   4 +-
 .../log4j/core/impl/MutableContextData.java     |  68 --
 .../log4j/core/impl/MutableLogEvent.java        |   3 +-
 .../log4j/core/impl/OpenHashMapContextData.java | 887 -------------------
 .../core/impl/ReusableLogEventFactory.java      |   1 +
 .../core/impl/ThreadContextDataInjector.java    |   4 +-
 .../ContextDataAsEntryListDeserializer.java     |   2 +-
 .../ContextDataAsEntryListSerializer.java       |   4 +-
 .../core/jackson/ContextDataDeserializer.java   |   2 +-
 .../core/jackson/ContextDataSerializer.java     |   4 +-
 .../log4j/core/jackson/LogEventJsonMixIn.java   |   2 +-
 .../jackson/LogEventWithContextListMixIn.java   |   2 +-
 .../logging/log4j/core/layout/GelfLayout.java   |   2 +-
 .../log4j/core/pattern/MdcPatternConverter.java |   4 +-
 .../logging/log4j/core/util/BiConsumer.java     |  19 -
 .../logging/log4j/core/util/TriConsumer.java    |  21 -
 .../core/appender/db/jpa/TestBaseEntity.java    |   2 +-
 .../ContextDataAttributeConverterTest.java      |   4 +-
 .../ContextDataJsonAttributeConverterTest.java  |   6 +-
 .../core/async/RingBufferLogEventTest.java      |   2 +-
 .../log4j/core/impl/ArrayContextDataTest.java   | 568 ------------
 .../log4j/core/impl/Log4jLogEventTest.java      |   2 +
 .../log4j/core/impl/MutableLogEventTest.java    |   2 +
 .../core/impl/OpenHashMapContextDataTest.java   | 506 -----------
 .../log4j/flume/appender/FlumeEvent.java        |   2 +-
 .../jmh/ArrayContextDataVsHashMapBenchmark.java |   8 +-
 49 files changed, 2997 insertions(+), 2661 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java
new file mode 100644
index 0000000..d934a3d
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ArrayContextData.java
@@ -0,0 +1,448 @@
+/*
+ * 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
+ *
+ *      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 org.apache.logging.log4j.spi;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.TriConsumer;
+
+/**
+ * Array-based implementation of the {@code ContextData} interface. Keys are 
held in a sorted array.
+ * <p>
+ * This is not a generic collection, but makes some trade-offs to optimize for 
the Log4j ContextData use case:
+ * </p>
+ * <ul>
+ *   <li>Garbage-free iteration over key-value pairs with {@code BiConsumer} 
and {@code TriConsumer}.</li>
+ *   <li>Fast copy. If the ThreadContextMap is also an instance of {@code 
ArrayContextData}, the full thread context
+ *     data can be transferred with two array copies and two field 
updates.</li>
+ *   <li>Acceptable performance for small data sets. The current 
implementation stores keys in a sorted array, values
+ *     are stored in a separate array at the same index.
+ *     Worst-case performance of {@code get} and {@code containsKey} is O(log 
N),
+ *     worst-case performance of {@code put} and {@code remove} is O(N log N).
+ *     The expectation is that for the small values of {@code N} (less than 
100) that are the vast majority of
+ *     ThreadContext use cases, the constants dominate performance more than 
the asymptotic performance of the
+ *     algorithms used.
+ *     </li>
+ *     <li>Compact representation.</li>
+ * </ul>
+ *
+ * @since 2.7
+ */
+public class ArrayContextData implements MutableContextData, ThreadContextMap {
+
+    /**
+     * The default initial capacity.
+     */
+    private static final int DEFAULT_INITIAL_CAPACITY = 4;
+    private static final long serialVersionUID = -5748905872274478116L;
+    private static final int HASHVAL = 31;
+
+    private static final TriConsumer<String, Object, MutableContextData> 
PUT_ALL = new TriConsumer<String, Object, MutableContextData>() {
+        @Override
+        public void accept(final String key, final Object value, final 
MutableContextData contextData) {
+            contextData.putValue(key, value);
+        }
+    };
+
+    /**
+     * An empty array instance to share when the table is not inflated.
+     */
+    private static final String[] EMPTY = {};
+
+    private transient String[] keys = EMPTY;
+    private transient Object[] values = EMPTY;
+
+    /**
+     * The number of key-value mappings contained in this map.
+     */
+    private transient int size;
+
+    /**
+     * The next size value at which to resize (capacity * load factor).
+     * @serial
+     */
+    // If table == EMPTY_TABLE then this is the initial capacity at which the
+    // table will be created when inflated.
+    private int threshold;
+
+    public ArrayContextData() {
+        this(DEFAULT_INITIAL_CAPACITY);
+    }
+
+    public ArrayContextData(final int initialCapacity) {
+        if (initialCapacity < 1) {
+            throw new IllegalArgumentException("Initial capacity must be at 
least one but was " + initialCapacity);
+        }
+        threshold = ceilingNextPowerOfTwo(initialCapacity);
+    }
+
+    public ArrayContextData(final ContextData other) {
+        if (other instanceof ArrayContextData) {
+            initFrom0((ArrayContextData) other);
+        } else if (other != null) {
+            resize(ceilingNextPowerOfTwo(other.size()));
+            other.forEach(PUT_ALL, this);
+        }
+    }
+
+    @Override
+    public void clear() {
+        Arrays.fill(keys, 0, size, null);
+        Arrays.fill(values, 0, size, null);
+        size = 0;
+    }
+
+    @Override
+    public boolean containsKey(final String key) {
+        return indexOfKey(key) >= 0;
+    }
+
+    @Override
+    public Map<String, String> asMap() {
+        final Map<String, String> result = new HashMap<>(size());
+        for (int i = 0; i < size(); i++) {
+            final Object value = getValueAt(i);
+            result.put(getKeyAt(i), value == null ? null : 
String.valueOf(value));
+        }
+        return result;
+    }
+
+    @Override
+    public Map<String, String> getCopy() {
+        return asMap();
+    }
+
+    @Override
+    public Map<String, String> getImmutableMapOrNull() {
+        return isEmpty() ? null : Collections.unmodifiableMap(asMap());
+    }
+
+    @Override
+    public String get(final String key) {
+        final Object result = getValue(key);
+        return result == null ? null : String.valueOf(result);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <V> V getValue(final String key) {
+        final int index = indexOfKey(key);
+        if (index < 0) {
+            return null;
+        }
+        return (V) values[index];
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    int indexOfKey(final String key) {
+        if (keys == EMPTY) {
+            return -1;
+        }
+        if (key == null) { // null key is located at the start of the array
+            return nullKeyIndex(); // insert at index zero
+        }
+        final int start = size > 0 && keys[0] == null ? 1 : 0;
+        return Arrays.binarySearch(keys, start, size, key);
+    }
+
+    private int nullKeyIndex() {
+        return size > 0 && keys[0] == null ? 0 : ~0;
+    }
+
+    @Override
+    public void put(final String key, final String value) {
+        putValue(key, value);
+    }
+
+    @Override
+    public void putValue(final String key, final Object value) {
+        if (keys == EMPTY) {
+            inflateTable(threshold);
+        }
+        final int index = indexOfKey(key);
+        if (index >= 0) {
+            keys[index] = key;
+            values[index] = value;
+        } else { // not found, so insert.
+            insertAt(~index, key, value);
+        }
+    }
+
+    private void insertAt(final int index, final String key, final Object 
value) {
+        ensureCapacity();
+        System.arraycopy(keys, index, keys, index + 1, size - index);
+        System.arraycopy(values, index, values, index + 1, size - index);
+        keys[index] = key;
+        values[index] = value;
+        size++;
+    }
+
+    @Override
+    public void putAll(final ContextData source) {
+        if (source instanceof ArrayContextData) {
+            initFrom0((ArrayContextData) source);
+        } else if (source != null) {
+            source.forEach(PUT_ALL, this);
+        }
+    }
+
+    public void initFrom(final ArrayContextData other) {
+        initFrom0(other);
+    }
+
+    private void initFrom0(final ArrayContextData other) {
+        if (keys.length < other.size) {
+            keys = new String[other.threshold];
+            values = new Object[other.threshold];
+        }
+        System.arraycopy(other.keys, 0, keys, 0, other.size);
+        System.arraycopy(other.values, 0, values, 0, other.size);
+
+        size = other.size;
+        threshold = other.threshold;
+    }
+
+    private void ensureCapacity() {
+        if (size >= threshold) {
+            resize(threshold * 2);
+        }
+    }
+
+    private void resize(final int newCapacity) {
+        final String[] oldKeys = keys;
+        final Object[] oldValues = values;
+
+        keys = new String[newCapacity];
+        values = new Object[newCapacity];
+
+        System.arraycopy(oldKeys, 0, keys, 0, size);
+        System.arraycopy(oldValues, 0, values, 0, size);
+
+        threshold = newCapacity;
+    }
+
+    /**
+     * Inflates the table.
+     */
+    private void inflateTable(int toSize) {
+        threshold = toSize;
+        keys = new String[toSize];
+        values = new Object[toSize];
+    }
+
+    @Override
+    public void remove(final String key) {
+        if (keys == EMPTY) {
+            return;
+        }
+        final int index = indexOfKey(key);
+        if (index >= 0) {
+            System.arraycopy(keys, index + 1, keys, index, size - index);
+            System.arraycopy(values, index + 1, values, index, size - index);
+            size--;
+        }
+    }
+
+    String getKeyAt(final int index) {
+        if (index < 0 || index >= size) {
+            return null;
+        }
+        return keys[index];
+    }
+
+    @SuppressWarnings("unchecked")
+    <V> V getValueAt(final int index) {
+        if (index < 0 || index >= size) {
+            return null;
+        }
+        return (V) values[index];
+    }
+
+    @Override
+    public int size() {
+        return size;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <V> void forEach(BiConsumer<String, ? super V> action) {
+        for (int i = 0; i < size; i++) {
+            action.accept(keys[i], (V) values[i]);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <V, T> void forEach(TriConsumer<String, ? super V, T> action, T 
state) {
+        for (int i = 0; i < size; i++) {
+            action.accept(keys[i], (V) values[i], state);
+        }
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof ArrayContextData)) {
+            return false;
+        }
+        ArrayContextData other = (ArrayContextData) obj;
+        if (this.size() != other.size()) {
+            return false;
+        }
+        for (int i = 0; i < size(); i++) {
+            if (!Objects.equals(keys[i], other.keys[i])) {
+                return false;
+            }
+            if (!Objects.equals(values[i], other.values[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 37;
+        result = HASHVAL * result + size;
+        result = HASHVAL * result + hashCode(keys, size);
+        result = HASHVAL * result + hashCode(values, size);
+        return result;
+    }
+
+    private static int hashCode(Object[] values, int length) {
+        int result = 1;
+        for (int i = 0; i < length; i++) {
+            result = HASHVAL * result + (values[i] == null ? 0 : 
values[i].hashCode());
+        }
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder(256);
+        sb.append('{');
+        for (int i = 0; i < size; i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(keys[i]).append('=');
+            sb.append(values[i] == this ? "(this map)" : values[i]);
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+
+    /**
+     * Save the state of the {@code ArrayContextData} instance to a stream 
(i.e.,
+     * serialize it).
+     *
+     * @serialData The <i>capacity</i> of the ArrayContextData (the length of 
the
+     *             bucket array) is emitted (int), followed by the
+     *             <i>size</i> (an int, the number of key-value
+     *             mappings), followed by the key (Object) and value (Object)
+     *             for each key-value mapping.  The key-value mappings are
+     *             emitted in no particular order.
+     */
+    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
+        // Write out the threshold, and any hidden stuff
+        s.defaultWriteObject();
+
+        // Write out number of buckets
+        if (keys == EMPTY) {
+            s.writeInt(ceilingNextPowerOfTwo(threshold));
+        } else {
+            s.writeInt(keys.length);
+        }
+
+        // Write out size (number of Mappings)
+        s.writeInt(size);
+
+        // Write out keys and values (alternating)
+        if (size > 0) {
+            for (int i = 0; i < size; i++) {
+                s.writeObject(keys[i]);
+                s.writeObject(values[i]);
+            }
+        }
+    }
+
+
+    /**
+     * Calculate the next power of 2, greater than or equal to x.
+     * <p>
+     * From Hacker's Delight, Chapter 3, Harry S. Warren Jr.
+     *
+     * @param x Value to round up
+     * @return The next power of 2 from x inclusive
+     */
+    private static int ceilingNextPowerOfTwo(final int x) {
+        final int BITS_PER_INT = 32;
+        return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1));
+    }
+
+    /**
+     * Reconstitute the {@code ArrayContextData} instance from a stream (i.e.,
+     * deserialize it).
+     */
+    private void readObject(java.io.ObjectInputStream s)  throws IOException, 
ClassNotFoundException {
+        // Read in the threshold (ignored), and any hidden stuff
+        s.defaultReadObject();
+
+        // set other fields that need values
+        keys = EMPTY;
+        values = EMPTY;
+
+        // Read in number of buckets
+        int capacity = s.readInt();
+        if (capacity < 0) {
+            throw new InvalidObjectException("Illegal capacity: " + capacity);
+        }
+
+        // Read number of mappings
+        int mappings = s.readInt();
+        if (mappings < 0) {
+            throw new InvalidObjectException("Illegal mappings count: " + 
mappings);
+        }
+
+        // allocate the bucket array;
+        if (mappings > 0) {
+            inflateTable(capacity);
+        } else {
+            threshold = capacity;
+        }
+
+        // Read the keys and values, and put the mappings in the arrays
+        for (int i = 0; i < mappings; i++) {
+            keys[i] = (String) s.readObject();
+            values[i] = s.readObject();
+        }
+        size = mappings;
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/spi/ContextData.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ContextData.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ContextData.java
new file mode 100644
index 0000000..d62b3f4
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ContextData.java
@@ -0,0 +1,115 @@
+/*
+ * 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
+ *
+ *      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 org.apache.logging.log4j.spi;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.TriConsumer;
+
+/**
+ * A read-only collection of context data. Context data items are String keys 
and values of arbitrary type that are
+ * set by the application to be included in all subsequent log events. A 
typical source of context data is the
+ * {@code ThreadContextMap} and the {@code Properties} defined in the 
configuration.
+ * <p>
+ * Applications can put custom data in this collection by installing a custom 
{@code ContextDataInjector}.
+ * </p>
+ *
+ * @see org.apache.logging.log4j.spi.ThreadContextMap
+ * @since 2.7
+ */
+public interface ContextData extends Serializable {
+    /**
+     * Returns a {@code Map<String, String>} view of this context data.
+     *
+     * @return a map view of this context data
+     */
+    Map<String, String> asMap();
+
+    /**
+     * Returns {@code true} if this context data contains the specified key, 
{@code false} otherwise.
+     *
+     * @param key the key whose presence to check. May be {@code null}.
+     * @return {@code true} if this context data contains the specified key, 
{@code false} otherwise
+     */
+    boolean containsKey(String key);
+
+    /**
+     * Performs the given action for each key-value pair in this data structure
+     * until all entries have been processed or the action throws an exception.
+     * <p>
+     * Some implementations may not support structural modifications (adding 
new elements or removing elements) while
+     * iterating over the contents. In such implementations, attempts to add 
or remove elements from the
+     * {@code BiConsumer}'s {@link BiConsumer#accept(Object, Object)} accept} 
method may cause a
+     * {@code ConcurrentModificationException} to be thrown.
+     * </p>
+     *
+     * @param action The action to be performed for each key-value pair in 
this collection
+     * @param <V> type of the value
+     * @throws java.util.ConcurrentModificationException some implementations 
may not support structural modifications
+     *          to this context data while iterating over the contents with 
{@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     */
+    <V> void forEach(final BiConsumer<String, ? super V> action);
+
+    /**
+     * Performs the given action for each key-value pair in this data structure
+     * until all entries have been processed or the action throws an exception.
+     * <p>
+     * The third parameter lets callers pass in a stateful object to be 
modified with the key-value pairs,
+     * so the TriConsumer implementation itself can be stateless and 
potentially reusable.
+     * </p>
+     * <p>
+     * Some implementations may not support structural modifications (adding 
new elements or removing elements) while
+     * iterating over the contents. In such implementations, attempts to add 
or remove elements from the
+     * {@code TriConsumer}'s {@link TriConsumer#accept(Object, Object, Object) 
accept} method may cause a
+     * {@code ConcurrentModificationException} to be thrown.
+     * </p>
+     *
+     * @param action The action to be performed for each key-value pair in 
this collection
+     * @param state the object to be passed as the third parameter to each 
invocation on the specified
+     *          triconsumer
+     * @param <V> type of the value
+     * @param <S> type of the third parameter
+     * @throws java.util.ConcurrentModificationException some implementations 
may not support structural modifications
+     *          to this context data while iterating over the contents with 
{@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     */
+    <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final 
S state);
+
+    /**
+     * Returns the value for the specified key, or {@code null} if the 
specified key does not exist in this collection.
+     *
+     * @param key the key whose value to return
+     * @return the value for the specified key or {@code null}
+     */
+    <V> V getValue(final String key);
+
+    /**
+     * Returns {@code true} if this collection is empty (size is zero), {@code 
false} otherwise.
+     * @return {@code true} if this collection is empty (size is zero)
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns the number of key-value pairs in this collection.
+     *
+     * @return the number of key-value pairs in this collection
+     */
+    int size();
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContext.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContext.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContext.java
new file mode 100644
index 0000000..be172b4
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContext.java
@@ -0,0 +1,156 @@
+/*
+ * 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
+ *
+ *      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 org.apache.logging.log4j.spi;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.logging.log4j.util.PropertiesUtil;
+
+/**
+ * ThreadContextMap implementation backed by {@code SortedArrayContextData}.
+ * A new ThreadContext Map is created each time it is updated and the Map 
stored is always
+ * immutable. This means the Map can be passed to other threads without 
concern that it will be updated. Since it is
+ * expected that the Map will be passed to many more log events than the 
number of keys it contains the performance
+ * should be much better than if the Map was copied for each event.
+ */
+public class CopyOnWriteSortedArrayThreadContext implements ThreadContextMap {
+    /**
+     * Property name ({@value} ) for selecting {@code InheritableThreadLocal} 
(value "true") or plain
+     * {@code ThreadLocal} (value is not "true") in the implementation.
+     */
+    public static final String INHERITABLE_MAP = 
"isThreadContextMapInheritable";
+
+    private final ThreadLocal<ArrayContextData> localMap;
+
+    public CopyOnWriteSortedArrayThreadContext() {
+        this.localMap = createThreadLocalMap();
+    }
+
+    // LOG4J2-479: by default, use a plain ThreadLocal, only use 
InheritableThreadLocal if configured.
+    // (This method is package protected for JUnit tests.)
+    static ThreadLocal<ArrayContextData> createThreadLocalMap() {
+        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
+        final boolean inheritable = 
managerProps.getBooleanProperty(INHERITABLE_MAP);
+        if (inheritable) {
+            return new InheritableThreadLocal<ArrayContextData>() {
+                @Override
+                protected ArrayContextData childValue(final ArrayContextData 
parentValue) {
+                    return parentValue != null ? new 
ArrayContextData(parentValue) : null;
+                }
+            };
+        }
+        // if not inheritable, return plain ThreadLocal with null as initial 
value
+        return new ThreadLocal<>();
+    }
+
+    @Override
+    public void put(final String key, final String value) {
+        ArrayContextData map = localMap.get();
+        map = map == null ? new ArrayContextData() : new ArrayContextData(map);
+        map.put(key, value);
+        localMap.set(map);
+    }
+
+    @Override
+    public String get(final String key) {
+        final ArrayContextData map = localMap.get();
+        return map == null ? null : map.get(key);
+    }
+
+    @Override
+    public void remove(final String key) {
+        final ArrayContextData map = localMap.get();
+        if (map != null) {
+            final ArrayContextData copy = new ArrayContextData(map);
+            copy.remove(key);
+            localMap.set(copy);
+        }
+    }
+
+    @Override
+    public void clear() {
+        localMap.remove();
+    }
+
+    @Override
+    public boolean containsKey(final String key) {
+        final ArrayContextData map = localMap.get();
+        return map != null && map.containsKey(key);
+    }
+
+    @Override
+    public Map<String, String> getCopy() {
+        final ArrayContextData map = localMap.get();
+        return map == null ? Collections.<String, String>emptyMap() : 
map.asMap();
+    }
+
+    public ContextData getContextData() {
+        return localMap.get();
+    }
+
+    @Override
+    public Map<String, String> getImmutableMapOrNull() {
+        final ArrayContextData map = localMap.get();
+        return map == null ? null : Collections.unmodifiableMap(map.asMap());
+    }
+
+    @Override
+    public boolean isEmpty() {
+        final ArrayContextData map = localMap.get();
+        return map == null || map.size() == 0;
+    }
+
+    @Override
+    public String toString() {
+        final ArrayContextData map = localMap.get();
+        return map == null ? "{}" : map.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        final ArrayContextData map = this.localMap.get();
+        result = prime * result + ((map == null) ? 0 : map.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof ThreadContextMap)) {
+            return false;
+        }
+        final ThreadContextMap other = (ThreadContextMap) obj;
+        final Map<String, String> map = this.getImmutableMapOrNull();
+        final Map<String, String> otherMap = other.getImmutableMapOrNull();
+        if (map == null) {
+            if (otherMap != null) {
+                return false;
+            }
+        } else if (!map.equals(otherMap)) {
+            return false;
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableContextData.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableContextData.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableContextData.java
new file mode 100644
index 0000000..8bc64e8
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableContextData.java
@@ -0,0 +1,68 @@
+/*
+ * 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
+ *
+ *      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 org.apache.logging.log4j.spi;
+
+import org.apache.logging.log4j.spi.ContextData;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.TriConsumer;
+
+/**
+ * Exposes methods to add and remove key-value pairs to and from {@code 
ContextData}.
+ *
+ * @see ContextData
+ * @since 2.7
+ */
+public interface MutableContextData extends ContextData {
+
+    /**
+     * Removes all key-value pairs from this collection.
+     * @throws java.util.ConcurrentModificationException some implementations 
may not support structural modifications
+     *          to this context data while iterating over the contents with 
{@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     */
+    void clear();
+
+    /**
+     * Puts the specified key-value pair into the collection.
+     *
+     * @param key the key to add or remove. Keys may be {@code null}.
+     * @param value the value to add. Values may be {@code null}.
+     * @throws java.util.ConcurrentModificationException some implementations 
may not support structural modifications
+     *          to this context data while iterating over the contents with 
{@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     */
+    void putValue(final String key, final Object value);
+
+    /**
+     * Copy all key-value pairs from the specified {@code ContextData} into 
this {@code MutableContextData}.
+     * @param source the {@code ContextData} to copy key-value pairs from
+     * @throws java.util.ConcurrentModificationException some implementations 
may not support structural modifications
+     *          to this context data while iterating over the contents with 
{@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     */
+    void putAll(final ContextData source);
+
+    /**
+     * Removes the key-value pair for the specified key from this context data 
collection.
+     *
+     * @param key the key to remove. May be {@code null}.
+     * @throws java.util.ConcurrentModificationException some implementations 
may not support structural modifications
+     *          to this context data while iterating over the contents with 
{@link #forEach(BiConsumer)} or
+     *          {@link #forEach(TriConsumer, Object)}.
+     */
+    void remove(final String key);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/spi/OpenHashMapContextData.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/OpenHashMapContextData.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/OpenHashMapContextData.java
new file mode 100644
index 0000000..9f27a6c
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/OpenHashMapContextData.java
@@ -0,0 +1,883 @@
+/*
+ * 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
+ *
+ *      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 org.apache.logging.log4j.spi;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.TriConsumer;
+
+/**
+ * Open hash map-based implementation of the {@code ContextData} interface.
+ * Implementation based on <a 
href="http://fastutil.di.unimi.it/";>fastutil</a>'s
+ * <a 
href="http://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/objects/Object2ObjectOpenHashMap.html";>Object2ObjectOpenHashMap</a>.
+ * <p>
+ * A type-specific hash map with a fast, small-footprint implementation.
+ *
+ * <P>
+ * Instances of this class use a hash table to represent a map. The table is
+ * filled up to a specified <em>load factor</em>, and then doubled in size to
+ * accommodate new entries. If the table is emptied below <em>one fourth</em> 
of
+ * the load factor, it is halved in size. However, halving is not performed 
when
+ * deleting entries from an iterator, as it would interfere with the iteration
+ * process.
+ *
+ * <p>
+ * Note that {@link #clear()} does not modify the hash table size. Rather, the
+ * {@link #trim(int)} method lets you control the size of
+ * the table; this is particularly useful if you reuse instances of this class.
+ * <p>
+ * <ul>
+ *   <li>Garbage-free iteration over key-value pairs with {@code BiConsumer} 
and {@code TriConsumer}.</li>
+ *   <li>Fast copy. If the ThreadContextMap is also an instance of {@code 
OpenHashMapContextData},
+ *     the full thread context data can be transferred with two array copies 
and five field updates.</li>
+ * </ul>
+ *
+ * @since 2.7
+ */
+public class OpenHashMapContextData<K, V> implements MutableContextData, 
ThreadContextMap {
+    /** The initial default size of a hash table. */
+    public static final int DEFAULT_INITIAL_SIZE = 16;
+
+    /** The default load factor of a hash table. */
+    public static final float DEFAULT_LOAD_FACTOR = .75f;
+
+    private static final long serialVersionUID = -1486744623338827187L;
+
+    /** The array of keys. */
+    protected transient K[] keys;
+    /** The array of values. */
+    protected transient V[] values;
+    /** The mask for wrapping a position counter. */
+    protected transient int mask;
+    /** Whether this set contains the key zero. */
+    protected transient boolean containsNullKey;
+    /** The current table size. */
+    protected transient int arraySize;
+    /**
+     * Threshold after which we rehash. It must be the table size times {@link 
#loadFactor}.
+     */
+    protected transient int maxFill;
+    /** Number of entries in the set (including the key zero, if present). */
+    protected int size;
+    /** The acceptable load factor. */
+    protected final float loadFactor;
+
+    private V defRetValue = null;
+
+    /**
+     * Creates a new hash map with initial expected
+     * {@link #DEFAULT_INITIAL_SIZE} entries and
+     * {@link #DEFAULT_LOAD_FACTOR} as load factor.
+     */
+    public OpenHashMapContextData() {
+        this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR);
+    }
+    /**
+     * Creates a new hash map with {@link #DEFAULT_LOAD_FACTOR} as load factor.
+     *
+     * @param expected
+     *            the expected number of elements in the hash map.
+     */
+    public OpenHashMapContextData(final int expected) {
+        this(expected, DEFAULT_LOAD_FACTOR);
+    }
+    /**
+     * Creates a new hash map.
+     *
+     * <p>
+     * The actual table size will be the least power of two greater than
+     * <code>expected</code>/<code>f</code>.
+     *
+     * @param expected
+     *            the expected number of elements in the hash set.
+     * @param f
+     *            the load factor.
+     */
+    @SuppressWarnings("unchecked")
+    public OpenHashMapContextData(final int expected, final float f) {
+        if (f <= 0 || f > 1) {
+            throw new IllegalArgumentException(
+                    "Load factor must be greater than 0 and smaller than or 
equal to 1");
+        }
+        if (expected < 0){
+            throw new IllegalArgumentException(
+                    "The expected number of elements must be nonnegative");
+        }
+        this.loadFactor = f;
+        arraySize = HashCommon.arraySize(expected, f);
+        mask = arraySize - 1;
+        maxFill = HashCommon.maxFill(arraySize, f);
+        keys = (K[]) new Object[arraySize + 1];
+        values = (V[]) new Object[arraySize + 1];
+    }
+    /**
+     * Creates a new hash map with {@link #DEFAULT_LOAD_FACTOR} as load
+     * factor copying a given one.
+     *
+     * @param map
+     *            a {@link Map} to be copied into the new hash map.
+     */
+    public OpenHashMapContextData(final Map<? extends K, ? extends V> map) {
+        this(map, DEFAULT_LOAD_FACTOR);
+    }
+    /**
+     * Creates a new hash map copying a given one.
+     *
+     * @param map
+     *            a {@link Map} to be copied into the new hash map.
+     * @param f
+     *            the load factor.
+     */
+    public OpenHashMapContextData(final Map<? extends K, ? extends V> map, 
final float f) {
+        this(map.size(), f);
+        putAll(map);
+    }
+
+    /**
+     * Creates a new hash map with {@link #DEFAULT_LOAD_FACTOR} as load
+     * factor copying a given type-specific one.
+     *
+     * @param contextData
+     *            a type-specific map to be copied into the new hash map.
+     */
+    public OpenHashMapContextData(final ContextData contextData) {
+        this(contextData, DEFAULT_LOAD_FACTOR);
+    }
+    /**
+     * Creates a new hash map copying a given type-specific one.
+     *
+     * @param contextData
+     *            a type-specific map to be copied into the new hash map.
+     * @param f
+     *            the load factor.
+     */
+    public OpenHashMapContextData(final ContextData contextData, final float 
f) {
+        this(contextData.size(), f);
+        if (contextData instanceof OpenHashMapContextData) {
+            initFrom0((OpenHashMapContextData) contextData);
+        } else {
+            contextData.forEach(PUT_ALL, this);
+        }
+    }
+    private static final TriConsumer<String, Object, MutableContextData> 
PUT_ALL =
+            new TriConsumer<String, Object, MutableContextData>() {
+        @Override
+        public void accept(final String key, final Object value, final 
MutableContextData contextData) {
+            contextData.putValue(key, value);
+        }
+    };
+
+    @SuppressWarnings("unchecked")
+    private void initFrom0(final OpenHashMapContextData other) {
+        // this.loadFactor = other.loadFactor; // final field
+        this.arraySize = other.arraySize;
+        this.size = other.size;
+        this.containsNullKey = other.containsNullKey;
+        this.mask = other.mask;
+        this.maxFill = other.maxFill;
+        keys = (K[]) Arrays.copyOf(other.keys, arraySize + 1);
+        values = (V[]) Arrays.copyOf(other.values, arraySize + 1);
+    }
+
+    private int realSize() {
+        return containsNullKey ? size - 1 : size;
+    }
+
+    private void ensureCapacity(final int capacity) {
+        final int needed = HashCommon.arraySize(capacity, loadFactor);
+        if (needed > arraySize) {
+            rehash(needed);
+        }
+    }
+
+    private void tryCapacity(final long capacity) {
+        final int needed = (int) Math.min(
+                1 << 30, Math.max(2, HashCommon.nextPowerOfTwo((int) 
Math.ceil(capacity / loadFactor))));
+        if (needed > arraySize) {
+            rehash(needed);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void putAll(Map<? extends K, ? extends V> map) {
+        if (loadFactor <= .5) {
+            // The resulting map will be sized for m.size() elements
+            ensureCapacity(map.size());
+        } else {
+            // The resulting map will be tentatively sized for size() +  
m.size() elements
+            tryCapacity(size() + map.size());
+        }
+        for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
+            putObjectValue(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Override
+    public Map<String, String> asMap() {
+        final Map<String, String> result = new HashMap<>(size);
+        forEach(COPY_INTO_MAP, result);
+        return result;
+    }
+
+    private static final TriConsumer<String, Object, Map<String, String>> 
COPY_INTO_MAP =
+            new TriConsumer<String, Object, Map<String, String>>() {
+        @Override
+        public void accept(final String k, final Object v, final Map<String, 
String> map) {
+            map.put(k, v == null ? null : v.toString());
+        }
+    };
+
+    /*
+     * Removes all elements from this map.
+     *
+     * <P>To increase object reuse, this method does not change the table size.
+     * If you want to reduce the table size, you must use {@link #trim()}.
+     */
+    @Override
+    public void clear() {
+        if (size == 0) {
+            return;
+        }
+        size = 0;
+        containsNullKey = false;
+        Arrays.fill(keys, (null));
+        Arrays.fill(values, null);
+    }
+
+    @Override
+    public boolean containsKey(final String key) {
+        return containsObjectKey((Object) key);
+    }
+
+    @SuppressWarnings("unchecked")
+    private boolean containsObjectKey(final Object k) {
+        if (k == null) {
+            return containsNullKey;
+        }
+        K curr;
+        final K[] key = this.keys;
+        int pos;
+        // The starting point.
+        if ((curr = key[pos = HashCommon.mix(k.hashCode()) & mask]) == null) {
+            return false;
+        }
+        if (k.equals(curr)) {
+            return true;
+        }
+        // There's always an unused entry.
+        while (true) {
+            if ((curr = key[pos = (pos + 1) & mask]) == null) {
+                return false;
+            }
+            if (k.equals(curr)) {
+                return true;
+            }
+        }
+    }
+
+    public boolean equals(final Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof ContextData)) {
+            return false;
+        }
+        final ContextData other = (ContextData) obj;
+        if (other.size() != size()) {
+            return false;
+        }
+        int pos = arraySize;
+        if (containsNullKey) {
+            if (!Objects.equals(getObjectValue(null), other.getValue(null))) {
+                return false;
+            }
+        }
+        --pos;
+        final K myKeys[] = this.keys;
+        for (; pos >= 0; pos--) {
+            K k;
+            if ((k = myKeys[pos]) != null) {
+                if (!Objects.equals(values[pos], other.getValue((String) k))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <VAL> void forEach(final BiConsumer<String, ? super VAL> action) {
+        final int startSize = size;
+        final K myKeys[] = this.keys;
+        int pos = arraySize;
+        if (containsNullKey) {
+            action.accept((String) myKeys[pos], (VAL) values[pos]);
+            if (size != startSize) {
+                throw new ConcurrentModificationException();
+            }
+        }
+        --pos;
+        for (; pos >= 0; pos--) {
+            if (myKeys[pos] != null) {
+                action.accept((String) myKeys[pos], (VAL) values[pos]);
+                if (size != startSize) {
+                    throw new ConcurrentModificationException();
+                }
+            }
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <VAL, STATE> void forEach(final TriConsumer<String, ? super VAL, 
STATE> action, final STATE state) {
+        final int startSize = size;
+        final K myKeys[] = this.keys;
+        int pos = arraySize;
+        if (containsNullKey) {
+            action.accept((String) myKeys[pos], (VAL) values[pos], state);
+            if (size != startSize) {
+                throw new ConcurrentModificationException();
+            }
+        }
+        --pos;
+        for (; pos >= 0; pos--) {
+            if (myKeys[pos] != null) {
+                action.accept((String) myKeys[pos], (VAL) values[pos], state);
+                if (size != startSize) {
+                    throw new ConcurrentModificationException();
+                }
+            }
+        }
+    }
+
+    @Override
+    public String get(final String key) {
+        return (String) getObjectValue(key);
+    }
+
+    @SuppressWarnings("unchecked")
+    private V getObjectValue(final Object k) {
+        if (k == null) {
+            return containsNullKey ? values[arraySize] : defRetValue;
+        }
+        K curr;
+        final K[] key = this.keys;
+        int pos;
+        // The starting point.
+        if ((curr = key[pos = HashCommon.mix(k.hashCode()) & mask]) == null) {
+            return defRetValue;
+        }
+        if (k.equals(curr)) {
+            return values[pos];
+        }
+        // There's always an unused entry.
+        while (true) {
+            if (((curr = key[pos = (pos + 1) & mask]) == null)) {
+                return defRetValue;
+            }
+            if (k.equals(curr)) {
+                return values[pos];
+            }
+        }
+    }
+
+    @Override
+    public Map<String, String> getCopy() {
+        return asMap();
+    }
+
+    @Override
+    public Map<String, String> getImmutableMapOrNull() {
+        return isEmpty() ? null : Collections.unmodifiableMap(asMap());
+    }
+
+    @Override
+    public <VAL> VAL getValue(final String key) {
+        return (VAL) getObjectValue((Object) key);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void put(final String key, final String value) {
+        putObjectValue((K) key, (V) value);
+    }
+
+    private int insert(final K k, final V v) {
+        int pos;
+        if (k == null) {
+            if (containsNullKey) {
+                return arraySize;
+            }
+            containsNullKey = true;
+            pos = arraySize;
+        } else {
+            K curr;
+            final K[] key = this.keys;
+            // The starting point.
+            if (!((curr = key[pos = HashCommon.mix(k.hashCode()) & mask]) == 
null)) {
+                if (curr.equals(k)) {
+                    return pos;
+                }
+                while (!((curr = key[pos = (pos + 1) & mask]) == null)) {
+                    if (curr.equals(k)) {
+                        return pos;
+                    }
+                }
+            }
+        }
+        keys[pos] = k;
+        values[pos] = v;
+        if (size++ >= maxFill) {
+            rehash(HashCommon.arraySize(size + 1, loadFactor));
+        }
+        return -1;
+    }
+
+    @Override
+    public void putAll(final ContextData source) {
+        if (size() == 0 && source instanceof OpenHashMapContextData) {
+            initFrom0((OpenHashMapContextData) source);
+        } else if (source != null) {
+            source.forEach(PUT_ALL, this);
+        }
+    }
+
+    private V putObjectValue(final K k, final V v) {
+        final int pos = insert(k, v);
+        if (pos < 0) {
+            return defRetValue;
+        }
+        final V oldValue = values[pos];
+        values[pos] = v;
+        return oldValue;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void putValue(final String key, final Object value) {
+        putObjectValue((K) key, (V) value);
+    }
+
+    @Override
+    public void remove(final String key) {
+        removeObjectKey((Object) key);
+    }
+
+    @SuppressWarnings("unchecked")
+    private V removeObjectKey(final Object k) {
+        if (k == null) {
+            if (containsNullKey) {
+                return removeNullEntry();
+            }
+            return defRetValue;
+        }
+        final K[] key = this.keys;
+        int pos = HashCommon.mix(k.hashCode()) & mask;
+        K curr = key[pos & mask];
+        // The starting point.
+        if (curr == null) {
+            return defRetValue;
+        }
+        if (k.equals(curr)) {
+            return removeEntry(pos);
+        }
+        while (true) {
+            if ((curr = key[pos = (pos + 1) & mask]) == null) {
+                return defRetValue;
+            }
+            if (k.equals(curr)) {
+                return removeEntry(pos);
+            }
+        }
+    }
+    private V removeEntry(final int pos) {
+        final V oldValue = values[pos];
+        values[pos] = null;
+        size--;
+        shiftKeys(pos);
+        if (size < maxFill / 4 && arraySize > DEFAULT_INITIAL_SIZE) {
+            rehash(arraySize / 2);
+        }
+        return oldValue;
+    }
+    private V removeNullEntry() {
+        containsNullKey = false;
+        keys[arraySize] = null;
+        final V oldValue = values[arraySize];
+        values[arraySize] = null;
+        size--;
+        if (size < maxFill / 4 && arraySize > DEFAULT_INITIAL_SIZE) {
+            rehash(arraySize / 2);
+        }
+        return oldValue;
+    }
+    /**
+     * Shifts left entries with the specified hash code, starting at the
+     * specified position, and empties the resulting free entry.
+     *
+     * @param pos
+     *            a starting position.
+     */
+    private void shiftKeys(int pos) {
+        // Shift entries with the same hash.
+        int last, slot;
+        K curr;
+        final K[] myKeys = this.keys;
+        for (;;) {
+            pos = ((last = pos) + 1) & mask;
+            for (;;) {
+                if (((curr = myKeys[pos]) == null)) {
+                    myKeys[last] = (null);
+                    values[last] = null;
+                    return;
+                }
+                slot = HashCommon.mix(curr.hashCode()) & mask;
+                if (last <= pos ? (last >= slot || slot > pos) : (last >= slot 
&& slot > pos)) {
+                    break;
+                }
+                pos = (pos + 1) & mask;
+            }
+            myKeys[last] = curr;
+            values[last] = values[pos];
+        }
+    }
+
+    @Override
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Rehashes this map if the table is too large.
+     *
+     * <P>
+     * Let <var>N</var> be the smallest table size that can hold
+     * <code>max(n,{@link #size()})</code> entries, still satisfying the load
+     * factor. If the current table size is smaller than or equal to
+     * <var>N</var>, this method does nothing. Otherwise, it rehashes this map
+     * in a table of size <var>N</var>.
+     *
+     * <P>
+     * This method is useful when reusing maps. {@linkplain #clear() Clearing a
+     * map} leaves the table size untouched. If you are reusing a map many 
times,
+     * you can call this method with a typical size to avoid keeping around a
+     * very large table just because of a few large transient maps.
+     *
+     * @param n
+     *            the threshold for the trimming.
+     * @return true if there was enough memory to trim the map.
+     */
+    public boolean trim(final int n) {
+        final int l = HashCommon.nextPowerOfTwo((int) Math.ceil(n / 
loadFactor));
+        if (l >= n || size > HashCommon.maxFill(l, loadFactor))
+            return true;
+        try {
+            rehash(l);
+        } catch (OutOfMemoryError cantDoIt) { // unusual to catch OOME but in 
this case appropriate
+            return false;
+        }
+        return true;
+    }
+    /**
+     * Rehashes the map.
+     *
+     * <P>
+     * This method implements the basic rehashing strategy, and may be 
overriden
+     * by subclasses implementing different rehashing strategies (e.g.,
+     * disk-based rehashing). However, you should not override this method
+     * unless you understand the internal workings of this class.
+     *
+     * @param newN
+     *            the new size
+     */
+    @SuppressWarnings("unchecked")
+    protected void rehash(final int newN) {
+        final K myKeys[] = this.keys;
+        final V myValues[] = this.values;
+        final int mask = newN - 1; // Note that this is used by the hashing
+        // macro
+        final K newKey[] = (K[]) new Object[newN + 1];
+        final V newValue[] = (V[]) new Object[newN + 1];
+        int i = arraySize, pos;
+        for (int j = realSize(); j-- != 0;) {
+            while (myKeys[--i] == null) {
+                // advance i until we find an existing key
+            }
+            if (newKey[pos = HashCommon.mix(myKeys[i].hashCode()) & mask] != 
null) { // rehash & check slot availability
+                while (newKey[pos = (pos + 1) & mask] != null) {
+                    // find available slot at (or immediately following) pos
+                }
+            }
+            newKey[pos] = myKeys[i];
+            newValue[pos] = myValues[i];
+        }
+        newValue[newN] = myValues[arraySize];
+        arraySize = newN;
+        this.mask = mask;
+        maxFill = HashCommon.maxFill(arraySize, loadFactor);
+        this.keys = newKey;
+        this.values = newValue;
+    }
+
+    /**
+     * Returns a hash code for this map.
+     *
+     * @return a hash code for this map.
+     */
+    public int hashCode() {
+        int result = 0;
+        for (int j = realSize(), i = 0, t = 0; j-- != 0;) {
+            while (keys[i] == null) {
+                i++;
+            }
+            if (this != keys[i]) {
+                t = keys[i].hashCode();
+            }
+            if (this != values[i]) {
+                t ^= (values[i] == null ? 0 : values[i].hashCode());
+            }
+            result += t;
+            i++;
+        }
+        // Zero / null keys have hash zero.
+        if (containsNullKey) {
+            result += (values[arraySize] == null ? 0 : 
values[arraySize].hashCode());
+        }
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    private void readObject(final ObjectInputStream s) throws IOException, 
ClassNotFoundException {
+        s.defaultReadObject();
+        arraySize = HashCommon.arraySize(size, loadFactor);
+        maxFill = HashCommon.maxFill(arraySize, loadFactor);
+        mask = arraySize - 1;
+        final K key[] = this.keys = (K[]) new Object[arraySize + 1];
+        final V value[] = this.values = (V[]) new Object[arraySize + 1];
+        K k;
+        V v;
+        for (int i = size, pos; i-- != 0;) {
+            k = (K) s.readObject();
+            v = (V) s.readObject();
+            if (k == null) {
+                pos = arraySize;
+                containsNullKey = true;
+            } else {
+                pos = HashCommon.mix(k.hashCode()) & mask;
+                while (key[pos] != null) {
+                    pos = (pos + 1) & mask;
+                }
+            }
+            key[pos] = k;
+            value[pos] = v;
+        }
+    }
+
+    private void writeObject(final ObjectOutputStream s) throws IOException {
+        s.defaultWriteObject();
+        try {
+            forEach(SERIALIZER, s);
+        } catch (final RuntimeException runex) {
+            if (runex.getCause() instanceof IOException) {
+                throw (IOException) runex.getCause();
+            }
+            throw runex;
+        }
+    }
+
+    private static final TriConsumer<String, Object, ObjectOutputStream> 
SERIALIZER =
+            new TriConsumer<String, Object, ObjectOutputStream>() {
+                @Override
+                public void accept(final String k, final Object v, final 
ObjectOutputStream objectOutputStream) {
+                    try {
+                        objectOutputStream.writeObject(k);
+                        objectOutputStream.writeObject(v);
+                    } catch (final IOException ioex) {
+                        throw new IllegalStateException(ioex);
+                    }
+                }
+            };
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder(256);
+        sb.append('{');
+        final K myKeys[] = this.keys;
+        int pos = arraySize;
+        boolean first = true;
+        if (containsNullKey) {
+            sb.append(myKeys[pos] == this ? "(this map)" : myKeys[pos]);
+            sb.append('=');
+            sb.append(values[pos] == this ? "(this map)" : values[pos]);
+            first = false;
+        }
+        --pos;
+        for (; pos >= 0; pos--) {
+            if (myKeys[pos] != null) {
+                if (first) {
+                    first = false;
+                } else {
+                    sb.append(", ");
+                }
+                sb.append(myKeys[pos] == this ? "(this map)" : myKeys[pos]);
+                sb.append('=');
+                sb.append(values[pos] == this ? "(this map)" : values[pos]);
+            }
+        }
+        sb.append('}');
+        return sb.toString();
+    }
+
+    private static class HashCommon {
+        private HashCommon() {}
+
+        /** 2<sup>32</sup> &middot; &phi;, &phi; = (&#x221A;5 &minus; 1)/2. */
+        private static final int INT_PHI = 0x9E3779B9;
+
+        /** The reciprocal of {@link #INT_PHI} modulo 2<sup>32</sup>. */
+        private static final int INV_INT_PHI = 0x144cbc89;
+
+        /** Avalanches the bits of an integer by applying the finalisation 
step of MurmurHash3.
+         *
+         * <p>This method implements the finalisation step of Austin Appleby's
+         * <a href="http://code.google.com/p/smhasher/";>MurmurHash3</a>.
+         * Its purpose is to avalanche the bits of the argument to within 
0.25% bias.
+         *
+         * @param x an integer.
+         * @return a hash value with good avalanching properties.
+         */
+        public static int murmurHash3(int x) {
+            x ^= x >>> 16;
+            x *= 0x85ebca6b;
+            x ^= x >>> 13;
+            x *= 0xc2b2ae35;
+            x ^= x >>> 16;
+            return x;
+        }
+
+        /**
+         * Quickly mixes the bits of an integer.
+         *
+         * <p>This method mixes the bits of the argument by multiplying by the 
golden ratio and
+         * xorshifting the result. It is borrowed from <a 
href="https://github.com/OpenHFT/Koloboke";>Koloboke</a>, and
+         * it has slightly worse behaviour than {@link #murmurHash3(int)} (in 
open-addressing hash tables the average
+         * number of probes is slightly larger), but it's much faster.
+         *
+         * @param x an integer.
+         * @return a hash value obtained by mixing the bits of {@code x}.
+         * @see #invMix(int)
+         */
+        public static int mix(final int x) {
+            final int h = x * INT_PHI;
+            return h ^ (h >>> 16);
+        }
+
+        /** The inverse of {@link #mix(int)}. This method is mainly useful to 
create unit tests.
+         *
+         * @param x an integer.
+         * @return a value that passed through {@link #mix(int)} would give 
{@code x}.
+         */
+        public static int invMix(final int x) {
+            return (x ^ x >>> 16) * INV_INT_PHI;
+        }
+
+        /** Return the least power of two greater than or equal to the 
specified value.
+         *
+         * <p>Note that this function will return 1 when the argument is 0.
+         *
+         * @param x an integer smaller than or equal to 2<sup>30</sup>.
+         * @return the least power of two greater than or equal to the 
specified value.
+         */
+        public static int nextPowerOfTwo(int x) {
+            if (x == 0) {
+                return 1;
+            }
+            x--;
+            x |= x >> 1;
+            x |= x >> 2;
+            x |= x >> 4;
+            x |= x >> 8;
+            return (x | x >> 16) + 1;
+        }
+
+        /** Return the least power of two greater than or equal to the 
specified value.
+         *
+         * <p>Note that this function will return 1 when the argument is 0.
+         *
+         * @param x a long integer smaller than or equal to 2<sup>62</sup>.
+         * @return the least power of two greater than or equal to the 
specified value.
+         */
+        public static long nextPowerOfTwo(long x) {
+            if (x == 0) {
+                return 1;
+            }
+            x--;
+            x |= x >> 1;
+            x |= x >> 2;
+            x |= x >> 4;
+            x |= x >> 8;
+            x |= x >> 16;
+            return (x | x >> 32) + 1;
+        }
+
+
+        /** Returns the maximum number of entries that can be filled before 
rehashing.
+         *
+         * @param n the size of the backing array.
+         * @param f the load factor.
+         * @return the maximum number of entries before rehashing.
+         */
+        public static int maxFill(final int n, final float f) {
+               /* We must guarantee that there is always at least
+                * one free entry (even with pathological load factors). */
+            return Math.min((int) Math.ceil(n * f), n - 1);
+        }
+
+        /**
+         * Returns the least power of two smaller than or equal to 
2<sup>30</sup> and larger than or equal to
+         * <code>Math.ceil( expected / f )</code>.
+         *
+         * @param expected the expected number of elements in a hash table.
+         * @param f the load factor.
+         * @return the minimum possible size for a backing array.
+         * @throws IllegalArgumentException if the necessary size is larger 
than 2<sup>30</sup>.
+         */
+        public static int arraySize(final int expected, final float f) {
+            final long result = Math.max(2, nextPowerOfTwo((long) 
Math.ceil(expected / f)));
+            if (result > (1 << 30)) {
+                throw new IllegalArgumentException("Too large (" + expected +
+                        " expected elements with load factor " + f + ")");
+            }
+            return (int) result;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/spi/SortedArrayThreadContext.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/SortedArrayThreadContext.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/SortedArrayThreadContext.java
new file mode 100644
index 0000000..f3438c5
--- /dev/null
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/SortedArrayThreadContext.java
@@ -0,0 +1,156 @@
+/*
+ * 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
+ *
+ *      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 org.apache.logging.log4j.spi;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.logging.log4j.util.PropertiesUtil;
+
+/**
+ * Garbage-free ThreadContextMap implementation backed by {@code 
SortedArrayContextData}.
+ */
+public class SortedArrayThreadContext implements ThreadContextMap {
+    /**
+     * Property name ({@value} ) for selecting {@code InheritableThreadLocal} 
(value "true") or plain
+     * {@code ThreadLocal} (value is not "true") in the implementation.
+     */
+    public static final String INHERITABLE_MAP = 
"isThreadContextMapInheritable";
+
+    private final ThreadLocal<ArrayContextData> localMap;
+
+    public SortedArrayThreadContext() {
+        this.localMap = createThreadLocalMap();
+    }
+
+    // LOG4J2-479: by default, use a plain ThreadLocal, only use 
InheritableThreadLocal if configured.
+    // (This method is package protected for JUnit tests.)
+    static ThreadLocal<ArrayContextData> createThreadLocalMap() {
+        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
+        final boolean inheritable = 
managerProps.getBooleanProperty(INHERITABLE_MAP);
+        if (inheritable) {
+            return new InheritableThreadLocal<ArrayContextData>() {
+                @Override
+                protected ArrayContextData childValue(final ArrayContextData 
parentValue) {
+                    return parentValue != null ? new 
ArrayContextData(parentValue) : null;
+                }
+            };
+        }
+        // if not inheritable, return plain ThreadLocal with null as initial 
value
+        return new ThreadLocal<>();
+    }
+
+    private ArrayContextData getThreadLocalMap() {
+        ArrayContextData map = localMap.get();
+        if (map == null) {
+            map = new ArrayContextData();
+            localMap.set(map);
+        }
+        return map;
+    }
+
+    @Override
+    public void put(final String key, final String value) {
+        getThreadLocalMap().put(key, value);
+    }
+
+    @Override
+    public String get(final String key) {
+        final ArrayContextData map = localMap.get();
+        return map == null ? null : map.get(key);
+    }
+
+    @Override
+    public void remove(final String key) {
+        final ArrayContextData map = localMap.get();
+        if (map != null) {
+            map.remove(key);
+        }
+    }
+
+    @Override
+    public void clear() {
+        localMap.remove();
+    }
+
+    @Override
+    public boolean containsKey(final String key) {
+        final ArrayContextData map = localMap.get();
+        return map != null && map.containsKey(key);
+    }
+
+    @Override
+    public Map<String, String> getCopy() {
+        final ArrayContextData map = localMap.get();
+        return map == null ? Collections.<String, String>emptyMap() : 
map.asMap();
+    }
+
+    public ContextData getContextData() {
+        return localMap.get();
+    }
+
+    @Override
+    public Map<String, String> getImmutableMapOrNull() {
+        final ArrayContextData map = localMap.get();
+        return map == null ? null : Collections.unmodifiableMap(map.asMap());
+    }
+
+    @Override
+    public boolean isEmpty() {
+        final ArrayContextData map = localMap.get();
+        return map == null || map.size() == 0;
+    }
+
+    @Override
+    public String toString() {
+        final ArrayContextData map = localMap.get();
+        return map == null ? "{}" : map.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        final ArrayContextData map = this.localMap.get();
+        result = prime * result + ((map == null) ? 0 : map.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof ThreadContextMap)) {
+            return false;
+        }
+        final ThreadContextMap other = (ThreadContextMap) obj;
+        final Map<String, String> map = this.getImmutableMapOrNull();
+        final Map<String, String> otherMap = other.getImmutableMapOrNull();
+        if (map == null) {
+            if (otherMap != null) {
+                return false;
+            }
+        } else if (!map.equals(otherMap)) {
+            return false;
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java
new file mode 100644
index 0000000..3dad0e4
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/BiConsumer.java
@@ -0,0 +1,19 @@
+package org.apache.logging.log4j.util;
+
+/**
+ * An operation that accepts two input arguments and returns no result.
+ *
+ * @param <K> type of the first argument
+ * @param <V> type of the second argument
+ * @see org.apache.logging.log4j.core.ContextData
+ * @since 2.7
+ */
+public interface BiConsumer<K, V> {
+
+    /**
+     * Performs the operation given the specified arguments.
+     * @param k the first input argument
+     * @param v the second input argument
+     */
+    void accept(K k, V v);
+}

http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/1118b27f/log4j-api/src/main/java/org/apache/logging/log4j/util/TriConsumer.java
----------------------------------------------------------------------
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/TriConsumer.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/TriConsumer.java
new file mode 100644
index 0000000..63960be
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/TriConsumer.java
@@ -0,0 +1,21 @@
+package org.apache.logging.log4j.util;
+
+/**
+ * An operation that accepts three input arguments and returns no result.
+ *
+ * @param <K> type of the first argument
+ * @param <V> type of the second argument
+ * @param <S> type of the third argument
+ * @see org.apache.logging.log4j.core.ContextData
+ * @since 2.7
+ */
+public interface TriConsumer<K, V, S> {
+
+    /**
+     * Performs the operation given the specified arguments.
+     * @param k the first input argument
+     * @param v the second input argument
+     * @param s the third input argument
+     */
+    void accept(K k, V v, S s);
+}

Reply via email to