From 32a2047a1adfcee9d357b1ecc421be2b5435ecca Mon Sep 17 00:00:00 2001 From: Ceki Gulcu Date: Fri, 3 Feb 2017 18:49:48 +0100 Subject: [PATCH] added COWArrayList --- .../core/spi/AppenderAttachableImpl.java | 11 +- .../core/spi/FilterAttachableImpl.java | 20 +- .../qos/logback/core/util/COWArrayList.java | 214 ++++++++++++++++++ .../logback/core/util/COWArrayListTest.java | 44 ++++ 4 files changed, 276 insertions(+), 13 deletions(-) create mode 100755 logback-core/src/main/java/ch/qos/logback/core/util/COWArrayList.java create mode 100755 logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListTest.java diff --git a/logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java b/logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java index 4c09ccc27..dee97112b 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java +++ b/logback-core/src/main/java/ch/qos/logback/core/spi/AppenderAttachableImpl.java @@ -14,9 +14,9 @@ package ch.qos.logback.core.spi; import java.util.Iterator; -import java.util.concurrent.CopyOnWriteArrayList; import ch.qos.logback.core.Appender; +import ch.qos.logback.core.util.COWArrayList; /** * A ReentrantReadWriteLock based implementation of the @@ -26,7 +26,8 @@ import ch.qos.logback.core.Appender; */ public class AppenderAttachableImpl implements AppenderAttachable { - final private CopyOnWriteArrayList> appenderList = new CopyOnWriteArrayList>(); + @SuppressWarnings("unchecked") + final private COWArrayList> appenderList = new COWArrayList>(new Appender[0]); /** * Attach an appender. If the appender is already in the list in won't be @@ -44,8 +45,10 @@ public class AppenderAttachableImpl implements AppenderAttachable { */ public int appendLoopOnAppenders(E e) { int size = 0; - for (Appender appender : appenderList) { - appender.doAppend(e); + final Appender[] appenderArray = appenderList.asTypedArray(); + final int len = appenderArray.length; + for (int i = 0; i < len; i++) { + appenderArray[i].doAppend(e); size++; } return size; diff --git a/logback-core/src/main/java/ch/qos/logback/core/spi/FilterAttachableImpl.java b/logback-core/src/main/java/ch/qos/logback/core/spi/FilterAttachableImpl.java index d7b5ec46b..ad0557e16 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/spi/FilterAttachableImpl.java +++ b/logback-core/src/main/java/ch/qos/logback/core/spi/FilterAttachableImpl.java @@ -15,9 +15,9 @@ package ch.qos.logback.core.spi; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.util.COWArrayList; /** * Implementation of FilterAttachable. @@ -26,7 +26,8 @@ import ch.qos.logback.core.filter.Filter; */ final public class FilterAttachableImpl implements FilterAttachable { - CopyOnWriteArrayList> filterList = new CopyOnWriteArrayList>(); + @SuppressWarnings("unchecked") + COWArrayList> filterList = new COWArrayList>(new Filter[0]); /** * Add a filter to end of the filter list. @@ -48,17 +49,18 @@ final public class FilterAttachableImpl implements FilterAttachable { * NEUTRAL, then NEUTRAL is returned. */ public FilterReply getFilterChainDecision(E event) { - // avoid Iterator creation if not necessary - if(filterList.isEmpty()) { - return FilterReply.NEUTRAL; - } - - for (Filter f : filterList) { - final FilterReply r = f.decide(event); + + final Filter[] filterArrray = filterList.asTypedArray(); + final int len = filterArrray.length; + + for (int i = 0; i < len; i++) { + final FilterReply r = filterArrray[i].decide(event); if (r == FilterReply.DENY || r == FilterReply.ACCEPT) { return r; } } + + // no decision return FilterReply.NEUTRAL; } diff --git a/logback-core/src/main/java/ch/qos/logback/core/util/COWArrayList.java b/logback-core/src/main/java/ch/qos/logback/core/util/COWArrayList.java new file mode 100755 index 000000000..74c2fa21a --- /dev/null +++ b/logback-core/src/main/java/ch/qos/logback/core/util/COWArrayList.java @@ -0,0 +1,214 @@ +package ch.qos.logback.core.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A GC-free lock-free thread-safe implementation of the {@link List} interface for use cases where iterations over the list vastly out-number modifications on the list. + * + *

Underneath, it wraps an instance of {@link CopyOnWriteArrayList} and exposes a copy of the array used by that instance. + * + *

Typical use:

+ * + *
+ *   COWArrayList list = new COWArrayList(new Integer[0]);
+ *   
+ *   // modify the list
+ *   list.add(1);
+ *   list.add(2);
+ *   
+ *   Integer[] intArray = list.asTypedArray();
+ *   int sum = 0;
+ *   // iteration over the array is thread-safe
+ *   for(int i = 0; i < intArray.length; i++) {
+ *     sum != intArray[i];
+ *   }
+ * 
+ * + *

If the list is not modified, then repetitive calls to {@link #asTypedArray()}, {@link #toArray()} and {@link #toArray(Object[])} + * are guaranteed to be GC-free. Note that iterating over the list using {@link COWArrayList#iterator()} and {@link COWArrayList#listIterator()} are not GC-free.

+ * + * @author Ceki Gulcu + * @since 1.1.10 + */ +public class COWArrayList implements List { + + AtomicBoolean fresh = new AtomicBoolean(false); + CopyOnWriteArrayList underlyingList = new CopyOnWriteArrayList(); + E[] copyOfArray; + final E[] modelArray; + + public COWArrayList(E[] modelArray) { + this.modelArray = modelArray; + } + + @Override + public int size() { + return underlyingList.size(); + } + + @Override + public boolean isEmpty() { + return underlyingList.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return underlyingList.contains(o); + } + + @Override + public Iterator iterator() { + return underlyingList.iterator(); + } + + private void refreshCopyIfNecessary() { + if (!isFresh()) { + refreshCopy(); + } + } + + private boolean isFresh() { + return fresh.get(); + } + + private void refreshCopy() { + copyOfArray = underlyingList.toArray(modelArray); + fresh.set(true); + } + + @Override + public Object[] toArray() { + refreshCopyIfNecessary(); + return copyOfArray; + } + + @SuppressWarnings("unchecked") + @Override + public T[] toArray(T[] a) { + refreshCopyIfNecessary(); + return (T[]) copyOfArray; + } + + /** + * Return an array of type E[]. The returned array is intended to be iterated over. + * If the list is modified, subsequent calls to this method will return different/modified + * array instances. + * + * @return + */ + public E[] asTypedArray() { + refreshCopyIfNecessary(); + return copyOfArray; + } + + private void markAsStale() { + fresh.set(false); + } + + public void addIfAbsent(E e) { + markAsStale(); + underlyingList.addIfAbsent(e); + } + + @Override + public boolean add(E e) { + markAsStale(); + return underlyingList.add(e); + } + + @Override + public boolean remove(Object o) { + markAsStale(); + return underlyingList.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return underlyingList.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + markAsStale(); + return underlyingList.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + markAsStale(); + return underlyingList.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + markAsStale(); + return underlyingList.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + markAsStale(); + return underlyingList.retainAll(c); + } + + @Override + public void clear() { + markAsStale(); + underlyingList.clear(); + } + + @Override + public E get(int index) { + refreshCopyIfNecessary(); + return (E) copyOfArray[index]; + } + + @Override + public E set(int index, E element) { + markAsStale(); + return underlyingList.set(index, element); + } + + @Override + public void add(int index, E element) { + markAsStale(); + underlyingList.add(index, element); + } + + @Override + public E remove(int index) { + markAsStale(); + return (E) underlyingList.remove(index); + } + + @Override + public int indexOf(Object o) { + return underlyingList.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return underlyingList.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return underlyingList.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return underlyingList.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return underlyingList.subList(fromIndex, toIndex); + } + +} diff --git a/logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListTest.java b/logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListTest.java new file mode 100755 index 000000000..d490c4cbd --- /dev/null +++ b/logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListTest.java @@ -0,0 +1,44 @@ +package ch.qos.logback.core.util; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class COWArrayListTest { + + Integer[] model = new Integer[0]; + COWArrayList cowaList = new COWArrayList(model); + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void basicToArray() { + cowaList.add(1); + Object[] result = cowaList.toArray(); + assertArrayEquals(new Integer[] { 1 }, result); + } + + @Test + public void basicToArrayWithModel() { + cowaList.add(1); + Integer[] result = cowaList.toArray(model); + assertArrayEquals(new Integer[] { 1 }, result); + } + + + @Test + public void basicToArrayTyped() { + cowaList.add(1); + Integer[] result = cowaList.asTypedArray(); + assertArrayEquals(new Integer[] { 1 }, result); + } + +} -- GitLab