changeset 38436:4676f6c9ebee

8029891: Deadlock detected in java/lang/ClassLoader/deadlock/GetResource.java Summary: Properties now stores values in an internal ConcurrentHashMap Reviewed-by: mchung, dholmes, plevart
author bchristi
date Thu, 19 May 2016 16:25:35 -0700
parents 292ad46c1bf1
children 0401899aa994
files jdk/src/java.base/share/classes/java/util/Hashtable.java jdk/src/java.base/share/classes/java/util/Properties.java jdk/test/ProblemList.txt jdk/test/java/lang/ClassLoader/deadlock/GetResource.java jdk/test/java/util/Properties/CheckOverrides.java jdk/test/java/util/Properties/CheckUnsynchronized.java jdk/test/java/util/Properties/PropertiesSerialization.java
diffstat 7 files changed, 702 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/jdk/src/java.base/share/classes/java/util/Hashtable.java	Thu May 19 16:05:33 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/util/Hashtable.java	Thu May 19 16:25:35 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -230,6 +230,14 @@
     }
 
     /**
+     * A constructor chained from {@link Properties} keeps Hashtable fields
+     * uninitialized since they are not used.
+     *
+     * @param dummy a dummy parameter
+     */
+    Hashtable(Void dummy) {}
+
+    /**
      * Returns the number of keys in this hashtable.
      *
      * @return  the number of keys in this hashtable.
@@ -549,18 +557,23 @@
      * @return  a clone of the hashtable
      */
     public synchronized Object clone() {
+        Hashtable<?,?> t = cloneHashtable();
+        t.table = new Entry<?,?>[table.length];
+        for (int i = table.length ; i-- > 0 ; ) {
+            t.table[i] = (table[i] != null)
+                ? (Entry<?,?>) table[i].clone() : null;
+        }
+        t.keySet = null;
+        t.entrySet = null;
+        t.values = null;
+        t.modCount = 0;
+        return t;
+    }
+
+    /** Calls super.clone() */
+    final Hashtable<?,?> cloneHashtable() {
         try {
-            Hashtable<?,?> t = (Hashtable<?,?>)super.clone();
-            t.table = new Entry<?,?>[table.length];
-            for (int i = table.length ; i-- > 0 ; ) {
-                t.table[i] = (table[i] != null)
-                    ? (Entry<?,?>) table[i].clone() : null;
-            }
-            t.keySet = null;
-            t.entrySet = null;
-            t.values = null;
-            t.modCount = 0;
-            return t;
+            return (Hashtable<?,?>)super.clone();
         } catch (CloneNotSupportedException e) {
             // this shouldn't happen, since we are Cloneable
             throw new InternalError(e);
@@ -1189,6 +1202,15 @@
      */
     private void writeObject(java.io.ObjectOutputStream s)
             throws IOException {
+        writeHashtable(s);
+    }
+
+    /**
+     * Perform serialization of the Hashtable to an ObjectOutputStream.
+     * The Properties class overrides this method.
+     */
+    void writeHashtable(java.io.ObjectOutputStream s)
+            throws IOException {
         Entry<Object, Object> entryStack = null;
 
         synchronized (this) {
@@ -1219,11 +1241,29 @@
     }
 
     /**
+     * Called by Properties to write out a simulated threshold and loadfactor.
+     */
+    final void defaultWriteHashtable(java.io.ObjectOutputStream s, int length,
+            float loadFactor) throws IOException {
+        this.threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
+        this.loadFactor = loadFactor;
+        s.defaultWriteObject();
+    }
+
+    /**
      * Reconstitute the Hashtable from a stream (i.e., deserialize it).
      */
     private void readObject(java.io.ObjectInputStream s)
-         throws IOException, ClassNotFoundException
-    {
+            throws IOException, ClassNotFoundException {
+        readHashtable(s);
+    }
+
+    /**
+     * Perform deserialization of the Hashtable from an ObjectInputStream.
+     * The Properties class overrides this method.
+     */
+    void readHashtable(java.io.ObjectInputStream s)
+            throws IOException, ClassNotFoundException {
         // Read in the threshold and loadFactor
         s.defaultReadObject();
 
--- a/jdk/src/java.base/share/classes/java/util/Properties.java	Thu May 19 16:05:33 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/util/Properties.java	Thu May 19 16:25:35 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -34,6 +34,13 @@
 import java.io.Writer;
 import java.io.OutputStreamWriter;
 import java.io.BufferedWriter;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.StreamCorruptedException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 
 import jdk.internal.util.xml.PropertiesDefaultHandler;
 
@@ -60,6 +67,13 @@
  * object that contains a non-{@code String} key.
  *
  * <p>
+ * The iterators returned by the {@code iterator} method of this class's
+ * "collection views" (that is, {@code entrySet()}, {@code keySet()}, and
+ * {@code values()}) may not fail-fast (unlike the Hashtable implementation).
+ * These iterators are guaranteed to traverse elements as they existed upon
+ * construction exactly once, and may (but are not guaranteed to) reflect any
+ * modifications subsequent to construction.
+ * <p>
  * The {@link #load(java.io.Reader) load(Reader)} {@code /}
  * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)}
  * methods load and store properties from and to a character based stream
@@ -128,6 +142,15 @@
     protected Properties defaults;
 
     /**
+     * Properties does not store values in its inherited Hashtable, but instead
+     * in an internal ConcurrentHashMap.  Synchronization is omitted from
+     * simple read operations.  Writes and bulk operations remain synchronized,
+     * as in Hashtable.
+     */
+    private transient ConcurrentHashMap<Object, Object> map =
+            new ConcurrentHashMap<>(8);
+
+    /**
      * Creates an empty property list with no default values.
      */
     public Properties() {
@@ -140,6 +163,9 @@
      * @param   defaults   the defaults.
      */
     public Properties(Properties defaults) {
+        // use package-private constructor to
+        // initialize unused fields with dummy values
+        super((Void) null);
         this.defaults = defaults;
     }
 
@@ -826,9 +852,9 @@
         bw.write("#" + new Date().toString());
         bw.newLine();
         synchronized (this) {
-            for (Enumeration<?> e = keys(); e.hasMoreElements();) {
-                String key = (String)e.nextElement();
-                String val = (String)get(key);
+            for (Map.Entry<Object, Object> e : entrySet()) {
+                String key = (String)e.getKey();
+                String val = (String)e.getValue();
                 key = saveConvert(key, true, escUnicode);
                 /* No need to escape embedded and trailing spaces for value, hence
                  * pass false to flag.
@@ -967,7 +993,7 @@
      * @see     #defaults
      */
     public String getProperty(String key) {
-        Object oval = super.get(key);
+        Object oval = map.get(key);
         String sval = (oval instanceof String) ? (String)oval : null;
         return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
     }
@@ -1029,7 +1055,7 @@
      * @since   1.6
      */
     public Set<String> stringPropertyNames() {
-        Hashtable<String, String> h = new Hashtable<>();
+        Map<String, String> h = new HashMap<>();
         enumerateStringProperties(h);
         return h.keySet();
     }
@@ -1044,11 +1070,11 @@
      */
     public void list(PrintStream out) {
         out.println("-- listing properties --");
-        Hashtable<String,Object> h = new Hashtable<>();
+        Map<String, Object> h = new HashMap<>();
         enumerate(h);
-        for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
-            String key = e.nextElement();
-            String val = (String)h.get(key);
+        for (Map.Entry<String, Object> e : h.entrySet()) {
+            String key = e.getKey();
+            String val = (String)e.getValue();
             if (val.length() > 40) {
                 val = val.substring(0, 37) + "...";
             }
@@ -1072,11 +1098,11 @@
      */
     public void list(PrintWriter out) {
         out.println("-- listing properties --");
-        Hashtable<String,Object> h = new Hashtable<>();
+        Map<String, Object> h = new HashMap<>();
         enumerate(h);
-        for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
-            String key = e.nextElement();
-            String val = (String)h.get(key);
+        for (Map.Entry<String, Object> e : h.entrySet()) {
+            String key = e.getKey();
+            String val = (String)e.getValue();
             if (val.length() > 40) {
                 val = val.substring(0, 37) + "...";
             }
@@ -1085,33 +1111,33 @@
     }
 
     /**
-     * Enumerates all key/value pairs in the specified hashtable.
-     * @param h the hashtable
+     * Enumerates all key/value pairs into the specified Map.
+     * @param h the Map
      * @throws ClassCastException if any of the property keys
      *         is not of String type.
      */
-    private synchronized void enumerate(Hashtable<String,Object> h) {
+    private void enumerate(Map<String, Object> h) {
         if (defaults != null) {
             defaults.enumerate(h);
         }
-        for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
-            String key = (String)e.nextElement();
-            h.put(key, get(key));
+        for (Map.Entry<Object, Object> e : entrySet()) {
+            String key = (String)e.getKey();
+            h.put(key, e.getValue());
         }
     }
 
     /**
-     * Enumerates all key/value pairs in the specified hashtable
+     * Enumerates all key/value pairs into the specified Map
      * and omits the property if the key or value is not a string.
-     * @param h the hashtable
+     * @param h the Map
      */
-    private synchronized void enumerateStringProperties(Hashtable<String, String> h) {
+    private void enumerateStringProperties(Map<String, String> h) {
         if (defaults != null) {
             defaults.enumerateStringProperties(h);
         }
-        for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
-            Object k = e.nextElement();
-            Object v = get(k);
+        for (Map.Entry<Object, Object> e : entrySet()) {
+            Object k = e.getKey();
+            Object v = e.getValue();
             if (k instanceof String && v instanceof String) {
                 h.put((String) k, (String) v);
             }
@@ -1130,4 +1156,283 @@
     private static final char[] hexDigit = {
         '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
     };
+
+    //
+    // Hashtable methods overridden and delegated to a ConcurrentHashMap instance
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public Enumeration<Object> keys() {
+        // CHM.keys() returns Iterator w/ remove() - instead wrap keySet()
+        return Collections.enumeration(map.keySet());
+    }
+
+    @Override
+    public Enumeration<Object> elements() {
+        // CHM.elements() returns Iterator w/ remove() - instead wrap values()
+        return Collections.enumeration(map.values());
+    }
+
+    @Override
+    public boolean contains(Object value) {
+        return map.contains(value);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        return map.containsValue(value);
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        return map.containsKey(key);
+    }
+
+    @Override
+    public Object get(Object key) {
+        return map.get(key);
+    }
+
+    @Override
+    public synchronized Object put(Object key, Object value) {
+        return map.put(key, value);
+    }
+
+    @Override
+    public synchronized Object remove(Object key) {
+        return map.remove(key);
+    }
+
+    @Override
+    public synchronized void putAll(Map<?, ?> t) {
+        map.putAll(t);
+    }
+
+    @Override
+    public synchronized void clear() {
+        map.clear();
+    }
+
+    @Override
+    public synchronized String toString() {
+        return map.toString();
+    }
+
+    @Override
+    public Set<Object> keySet() {
+        return Collections.synchronizedSet(map.keySet(), this);
+    }
+
+    @Override
+    public Collection<Object> values() {
+        return Collections.synchronizedCollection(map.values(), this);
+    }
+
+    @Override
+    public Set<Map.Entry<Object, Object>> entrySet() {
+        return Collections.synchronizedSet(new EntrySet(map.entrySet()), this);
+    }
+
+    /*
+     * Properties.entrySet() should not support add/addAll, however
+     * ConcurrentHashMap.entrySet() provides add/addAll.  This class wraps the
+     * Set returned from CHM, changing add/addAll to throw UOE.
+     */
+    private static class EntrySet implements Set<Map.Entry<Object, Object>> {
+        private Set<Map.Entry<Object,Object>> entrySet;
+
+        private EntrySet(Set<Map.Entry<Object, Object>> entrySet) {
+            this.entrySet = entrySet;
+        }
+
+        @Override public int size() { return entrySet.size(); }
+        @Override public boolean isEmpty() { return entrySet.isEmpty(); }
+        @Override public boolean contains(Object o) { return entrySet.contains(o); }
+        @Override public Object[] toArray() { return entrySet.toArray(); }
+        @Override public <T> T[] toArray(T[] a) { return entrySet.toArray(a); }
+        @Override public void clear() { entrySet.clear(); }
+        @Override public boolean remove(Object o) { return entrySet.remove(o); }
+
+        @Override
+        public boolean add(Map.Entry<Object, Object> e) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean addAll(Collection<? extends Map.Entry<Object, Object>> c) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean containsAll(Collection<?> c) {
+            return entrySet.containsAll(c);
+        }
+
+        @Override
+        public boolean removeAll(Collection<?> c) {
+            return entrySet.removeAll(c);
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> c) {
+            return entrySet.retainAll(c);
+        }
+
+        @Override
+        public Iterator<Map.Entry<Object, Object>> iterator() {
+            return entrySet.iterator();
+        }
+    }
+
+    @Override
+    public synchronized boolean equals(Object o) {
+        return map.equals(o);
+    }
+
+    @Override
+    public synchronized int hashCode() {
+        return map.hashCode();
+    }
+
+    @Override
+    public Object getOrDefault(Object key, Object defaultValue) {
+        return map.getOrDefault(key, defaultValue);
+    }
+
+    @Override
+    public synchronized void forEach(BiConsumer<? super Object, ? super Object> action) {
+        map.forEach(action);
+    }
+
+    @Override
+    public synchronized void replaceAll(BiFunction<? super Object, ? super Object, ?> function) {
+        map.replaceAll(function);
+    }
+
+    @Override
+    public synchronized Object putIfAbsent(Object key, Object value) {
+        return map.putIfAbsent(key, value);
+    }
+
+    @Override
+    public synchronized boolean remove(Object key, Object value) {
+        return map.remove(key, value);
+    }
+
+    /** @hidden */
+    @Override
+    public synchronized boolean replace(Object key, Object oldValue, Object newValue) {
+        return map.replace(key, oldValue, newValue);
+    }
+
+    @Override
+    public synchronized Object replace(Object key, Object value) {
+        return map.replace(key, value);
+    }
+
+    @Override
+    public synchronized Object computeIfAbsent(Object key,
+            Function<? super Object, ?> mappingFunction) {
+        return map.computeIfAbsent(key, mappingFunction);
+    }
+
+    @Override
+    public synchronized Object computeIfPresent(Object key,
+            BiFunction<? super Object, ? super Object, ?> remappingFunction) {
+        return map.computeIfPresent(key, remappingFunction);
+    }
+
+    @Override
+    public synchronized Object compute(Object key,
+            BiFunction<? super Object, ? super Object, ?> remappingFunction) {
+        return map.compute(key, remappingFunction);
+    }
+
+    @Override
+    public synchronized Object merge(Object key, Object value,
+            BiFunction<? super Object, ? super Object, ?> remappingFunction) {
+        return map.merge(key, value, remappingFunction);
+    }
+
+    //
+    // Special Hashtable methods
+
+    @Override
+    protected void rehash() { /* no-op */ }
+
+    @Override
+    public synchronized Object clone() {
+        Properties clone = (Properties) cloneHashtable();
+        clone.map = new ConcurrentHashMap<>(map);
+        return clone;
+    }
+
+    //
+    // Hashtable serialization overrides
+    // (these should emit and consume Hashtable-compatible stream)
+
+    @Override
+    void writeHashtable(ObjectOutputStream s) throws IOException {
+        List<Object> entryStack = new ArrayList<>(map.size() * 2); // an estimate
+
+        for (Map.Entry<Object, Object> entry : map.entrySet()) {
+            entryStack.add(entry.getValue());
+            entryStack.add(entry.getKey());
+        }
+
+        // Write out the simulated threshold, loadfactor
+        float loadFactor = 0.75f;
+        int count = entryStack.size() / 2;
+        int length = (int)(count / loadFactor) + (count / 20) + 3;
+        if (length > count && (length & 1) == 0) {
+            length--;
+        }
+        synchronized (map) { // in case of multiple concurrent serializations
+            defaultWriteHashtable(s, length, loadFactor);
+        }
+
+        // Write out simulated length and real count of elements
+        s.writeInt(length);
+        s.writeInt(count);
+
+        // Write out the key/value objects from the stacked entries
+        for (int i = entryStack.size() - 1; i >= 0; i--) {
+            s.writeObject(entryStack.get(i));
+        }
+    }
+
+    @Override
+    void readHashtable(ObjectInputStream s) throws IOException,
+            ClassNotFoundException {
+        // Read in the threshold and loadfactor
+        s.defaultReadObject();
+
+        // Read the original length of the array and number of elements
+        int origlength = s.readInt();
+        int elements = s.readInt();
+
+        // Validate # of elements
+        if (elements < 0) {
+            throw new StreamCorruptedException("Illegal # of Elements: " + elements);
+        }
+
+        // create CHM of appropriate capacity
+        map = new ConcurrentHashMap<>(elements);
+
+        // Read all the key/value objects
+        for (; elements > 0; elements--) {
+            Object key = s.readObject();
+            Object value = s.readObject();
+            map.put(key, value);
+        }
+    }
 }
--- a/jdk/test/ProblemList.txt	Thu May 19 16:05:33 2016 -0700
+++ b/jdk/test/ProblemList.txt	Thu May 19 16:25:35 2016 -0700
@@ -126,8 +126,6 @@
 
 # jdk_lang
 
-java/lang/ClassLoader/deadlock/GetResource.java                 8029891 generic-all
-
 java/lang/StringCoding/CheckEncodings.sh                        7008363 generic-all
 
 ############################################################################
--- a/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java	Thu May 19 16:05:33 2016 -0700
+++ b/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java	Thu May 19 16:25:35 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2016 Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -28,7 +28,7 @@
 import java.net.URL;
 
 /* @test
- * @bug 6977738
+ * @bug 6977738 8029891
  * @summary Test ClassLoader.getResource() that should not deadlock
  #          if another thread is holding the system properties object
  *
@@ -70,10 +70,6 @@
                     go.await();  // wait until t1 holds the lock of the system properties
 
                     URL u1 = Thread.currentThread().getContextClassLoader().getResource("unknownresource");
-                    URL u2 = Thread.currentThread().getContextClassLoader().getResource("sun/util/resources/CalendarData.class");
-                    if (u2 == null) {
-                        throw new RuntimeException("Test failed: resource not found");
-                    }
                     done.await();
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Properties/CheckOverrides.java	Thu May 19 16:25:35 2016 -0700
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @bug 8029891
+ * @summary Test that the Properties class overrides all public+protected
+ *          methods of all ancestor classes and interfaces
+ * @run main CheckOverrides
+ */
+public class CheckOverrides {
+
+    public static void main(String[] args) {
+        Set<MethodSignature> pMethodSignatures =
+            Stream.of(Properties.class.getDeclaredMethods())
+                .filter(CheckOverrides::isMethodOfInterest)
+                .map(MethodSignature::new)
+                .collect(Collectors.toSet());
+
+        Map<MethodSignature, Method> unoverriddenMethods = new HashMap<>();
+        for (Class<?> superclass = Properties.class.getSuperclass();
+             superclass != Object.class;
+             superclass = superclass.getSuperclass()) {
+            Stream.of(superclass.getDeclaredMethods())
+                .filter(CheckOverrides::isMethodOfInterest)
+                .forEach(m -> unoverriddenMethods.putIfAbsent(new MethodSignature(m), m));
+        }
+        unoverriddenMethods.keySet().removeAll(pMethodSignatures);
+
+        if (!unoverriddenMethods.isEmpty()) {
+            throw new RuntimeException(
+                "The following methods should be overridden by Properties class:\n" +
+                    unoverriddenMethods.values().stream()
+                        .map(Method::toString)
+                        .collect(Collectors.joining("\n  ", "  ", "\n"))
+            );
+        }
+    }
+
+    static boolean isMethodOfInterest(Method method) {
+        int mods = method.getModifiers();
+        return !Modifier.isStatic(mods) &&
+            (Modifier.isPublic(mods) || Modifier.isProtected(mods));
+    }
+
+    static class MethodSignature {
+        final Class<?> returnType;
+        final String name;
+        final Class<?>[] parameterTypes;
+
+        MethodSignature(Method method) {
+            this(method.getReturnType(), method.getName(), method.getParameterTypes());
+        }
+
+        private MethodSignature(Class<?> returnType, String name, Class<?>[] parameterTypes) {
+            this.returnType = returnType;
+            this.name = name;
+            this.parameterTypes = parameterTypes;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            MethodSignature that = (MethodSignature) o;
+            if (!returnType.equals(that.returnType)) return false;
+            if (!name.equals(that.name)) return false;
+            return Arrays.equals(parameterTypes, that.parameterTypes);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = returnType.hashCode();
+            result = 31 * result + name.hashCode();
+            result = 31 * result + Arrays.hashCode(parameterTypes);
+            return result;
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Properties/CheckUnsynchronized.java	Thu May 19 16:25:35 2016 -0700
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+
+/*
+ * @test
+ * @bug 8029891
+ * @summary Test Properties methods that do not synchronize any more
+ * @run main CheckUnsynchronized
+ */
+public class CheckUnsynchronized {
+    public static void main(String[] args) {
+        Properties props = new Properties();
+        synchronized (props) {
+            props.setProperty("key", "value");
+            System.out.println("contains(value)? " +
+                    CompletableFuture.supplyAsync(() -> props.contains("value")).join());
+            System.out.println("containsKey(key)? " +
+                    CompletableFuture.supplyAsync(() -> props.containsKey("key")).join());
+            System.out.println("containsValue(value)? " +
+                    CompletableFuture.supplyAsync(() -> props.containsValue("value")).join());
+            Enumeration<Object> elems =
+                    CompletableFuture.supplyAsync(() -> props.elements()).join();
+            System.out.println("first value from elements(): " + elems.nextElement());
+            System.out.println("value from get(): " +
+                    CompletableFuture.supplyAsync(() -> props.getProperty("key")).join());
+            System.out.println("getOrDefault(\"missing\"): " +
+                    CompletableFuture.supplyAsync(() -> props.getOrDefault("missing", "default")).join());
+            System.out.println("isEmpty()? " +
+                    CompletableFuture.supplyAsync(() -> props.isEmpty()).join());
+            Enumeration<Object> keys =
+                    CompletableFuture.supplyAsync(() -> props.keys()).join();
+            System.out.println("first key from keys(): " + keys.nextElement());
+            System.out.println("size(): " +
+                    CompletableFuture.supplyAsync(() -> props.size()).join());
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/Properties/PropertiesSerialization.java	Thu May 19 16:25:35 2016 -0700
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Base64;
+import java.util.Properties;
+
+/**
+ * @test
+ * @bug 8029891
+ * @summary tests the compatibility of Properties serial form
+ * @run main PropertiesSerialization read
+ *
+ * To update this test in case the serial form of Properties changes, run this
+ * test with the 'write' flag, and copy the resulting output back into this
+ * file, replacing the existing String declaration(s).
+ */
+public class PropertiesSerialization {
+    private static final Properties TEST_PROPS;
+    static {
+        TEST_PROPS = new Properties();
+        TEST_PROPS.setProperty("one", "two");
+        TEST_PROPS.setProperty("buckle", "shoe");
+        TEST_PROPS.setProperty("three", "four");
+        TEST_PROPS.setProperty("shut", "door");
+    }
+
+    /**
+     * Base64 encoded string for Properties object
+     * Java version: 1.8.0
+     **/
+    private static final String TEST_SER_BASE64 =
+         "rO0ABXNyABRqYXZhLnV0aWwuUHJvcGVydGllczkS0HpwNj6YAgABTAAIZGVmYXVs"
+       + "dHN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHIAE2phdmEudXRpbC5IYXNodGFi"
+       + "bGUTuw8lIUrkuAMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAI"
+       + "dwgAAAALAAAABHQAA29uZXQAA3R3b3QABHNodXR0AARkb29ydAAGYnVja2xldAAE"
+       + "c2hvZXQABXRocmVldAAEZm91cnhw";
+
+    public static void main(String[] args) throws IOException,
+            ClassNotFoundException {
+        if (args.length == 0) {
+            System.err.println("Run with 'read' or 'write'");
+            System.err.println("  read mode:  normal test mode.");
+            System.err.println("              Confirms that serial stream can");
+            System.err.println("              be deserialized as expected.");
+            System.err.println("  write mode: meant for updating the test,");
+            System.err.println("              should the serial form change.");
+            System.err.println("              Test output should be pasted");
+            System.err.println("              back into the test source.");
+            return;
+        }
+
+        Properties deserializedObject;
+        if ("read".equals(args[0])) {
+            ByteArrayInputStream bais = new
+              ByteArrayInputStream(Base64.getDecoder().decode(TEST_SER_BASE64));
+            try (ObjectInputStream ois = new ObjectInputStream(bais)) {
+                deserializedObject = (Properties) ois.readObject();
+            }
+            if (!TEST_PROPS.equals(deserializedObject)) {
+                throw new RuntimeException("deserializedObject not equals()");
+            }
+            System.out.println("Test passed");
+        } else if ("write".equals(args[0])) {
+            System.out.println("\nTo update the test, paste the following back "
+                    + "into the test code:\n");
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(TEST_PROPS);
+            oos.flush();
+            oos.close();
+
+            byte[] byteArray = baos.toByteArray();
+            // Check that the Properties deserializes correctly
+            ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
+            ObjectInputStream ois = new ObjectInputStream(bais);
+            Properties deser = (Properties)ois.readObject();
+            if (!TEST_PROPS.equals(deser)) {
+                throw new RuntimeException("write: Deserialized != original");
+            }
+
+            // Now get a Base64 string representation of the serialized bytes.
+            final String base64 = Base64.getEncoder().encodeToString(byteArray);
+            // Check that we can deserialize the Base64 string we just computed.
+            ByteArrayInputStream bais2 =
+              new ByteArrayInputStream(Base64.getDecoder().decode(base64));
+            ObjectInputStream ois2 = new ObjectInputStream(bais2);
+            Properties deser2 = (Properties)ois2.readObject();
+            if (!TEST_PROPS.equals(deser2)) {
+                throw new RuntimeException("write: Deserialized base64 != "
+                        + "original");
+            }
+            System.out.println(dumpBase64SerialStream(base64));
+        }
+    }
+
+    private static final String INDENT = "   ";
+    /* Based on:
+     * java/util/logging/HigherResolutionTimeStamps/SerializeLogRecored.java
+     */
+    private static String dumpBase64SerialStream(String base64) {
+        // Generates the Java Pseudo code that can be cut & pasted into
+        // this test (see Jdk8SerializedLog and Jdk9SerializedLog below)
+        final StringBuilder sb = new StringBuilder();
+        sb.append(INDENT).append(" /**").append('\n');
+        sb.append(INDENT).append("  * Base64 encoded string for Properties object\n");
+        sb.append(INDENT).append("  * Java version: ")
+                .append(System.getProperty("java.version")).append('\n');
+        sb.append(INDENT).append("  **/").append('\n');
+        sb.append(INDENT).append(" private static final String TEST_SER_BASE64 = ")
+                .append("\n").append(INDENT).append("      ");
+        final int last = base64.length() - 1;
+        for (int i=0; i<base64.length();i++) {
+            if (i%64 == 0) sb.append("\"");
+            sb.append(base64.charAt(i));
+            if (i%64 == 63 || i == last) {
+                sb.append("\"");
+                if (i == last) sb.append(";\n");
+                else sb.append("\n").append(INDENT).append("    + ");
+            }
+        }
+        return sb.toString();
+    }
+}