title: Java 8: removing duplicate after addall twice, possible concurrency issue?
body: <p>I know this is an old topic, but the following strange test case looks NEW, at least to myself, thanks in advance!</p>
  
  <p>Problem:
  I am expecting duplicate items will be removed, but the result is NOT stable, i.e, if run several times, then results are not same. There could be a concurrency issue behind this codes.</p>
  
  <p>if add duplicate item right one after another, then the code is fine.
  But, if using addall twice (just for testing purpose), then the expecting result is NOT stable.</p>
  
  <pre><code>import java.util.ArrayList;
  import java.util.Comparator;
  import java.util.List;
  import java.util.Objects;
  import java.util.concurrent.ConcurrentSkipListSet;
  import java.util.stream.Collectors;
  
  public class TestJava {
  
      public static void main(String[] args) {
          List&lt;TestObj&gt; result = new ArrayList&lt;&gt;();
  
          List&lt;TestObj&gt; tempList = new ArrayList&lt;&gt;();
          List&lt;TestObj&gt; tempList2 = new ArrayList&lt;&gt;();
          List&lt;TestObj&gt; tempResult = new ArrayList&lt;&gt;();
  
          int size = 3;
  
          for (int i = 0; i &lt; size; i++) {
              TestObj testObj = new TestObj();
              testObj.setField1("555");
              testObj.setField2("" + i);
              tempList.add(testObj);
  
              TestObj testObj2 = new TestObj();
              testObj2.setField1("555");
              testObj2.setField2("" + i);
              tempList2.add(testObj2);
  
              // tempResult.add(testObj); // &lt;-- if use this, then no concurrency issue
              // tempResult.add(testObj2);
          }
  
          tempResult.addAll(tempList);
          tempResult.addAll(tempList2);
  
          System.out.println("expecting 6, actual = " + tempResult.size());
  
          FilterComparator comp = new FilterComparator();
  
          result = tempResult.stream() //
                  .filter(new ConcurrentSkipListSet&lt;&gt;(comp)::add)//
                  .collect(Collectors.toList());
          System.out.println("expecting 3, actual = " + result.size()); // &lt;-- Here, looks like there is a concurrency issue! 
      }
  
      public static class TestObj {
          private String field1;
          private String field2;
          private String field3;
  
          public String getField1() {
              return field1;
          }
  
          public void setField1(String field1) {
              this.field1 = field1;
          }
  
          public String getField2() {
              return field2;
          }
  
          public void setField2(String field2) {
              this.field2 = field2;
          }
  
          public String getField3() {
              return field3;
          }
  
          public void setField3(String field3) {
              this.field3 = field3;
          }
  
      }
  
      public static class FilterComparator implements Comparator&lt;TestObj&gt; {
          public int compare(TestObj o1, TestObj o2) {
              if (compareIfSameString(o1.getField1(), o2.getField1()) //
                      &amp;&amp; compareIfSameString(o1.getField2(), o2.getField2()) //
              ) {
                  return 0;
              }
  
              return 1;
          }
      }
  
      public static boolean compareIfSameString(String oldValue, String newValue) {
          if (isReallyEmptyOrNull(oldValue) &amp;&amp; isReallyEmptyOrNull(newValue)) {
              return true;
          }
  
          return Objects.equals(oldValue, newValue);
      }
  
      public static boolean isReallyEmptyOrNull(String value) {
          return value == null || "".equals(value.trim());
      }
  
  }
  </code></pre>
  
  <p>Update: for later on reference
  thanks JB Nizet for your comment. I modified comparator as following and everything is fine!</p>
  
  <pre><code>public static class FilterComparator implements Comparator&lt;TestObj&gt; {
      public int compare(TestObj o1, TestObj o2) {
          if (o1.getField1() == null) {
              if (o2.getField1() != null) {
                  return o2.getField1().compareTo(o1.getField1());
              }
          } else if (o1.getField1().compareTo(o2.getField1()) != 0) {
              return o1.getField1().compareTo(o2.getField1());
          }
  
          if (o1.getField2() == null) {
              if (o2.getField2() != null) {
                  return o2.getField2().compareTo(o1.getField2());
              }
          } else if (o1.getField2().compareTo(o2.getField2()) != 0) {
              return o1.getField2().compareTo(o2.getField2());
          }
  
          if (o1.getField3() == null) {
              if (o2.getField3() != null) {
                  return o2.getField3().compareTo(o1.getField3());
              }
          } else if (o1.getField3().compareTo(o2.getField3()) != 0) {
              return o1.getField3().compareTo(o2.getField3());
          }
          return 0;
  
      }
  }
  </code></pre>