diff --git a/src/java.base/share/classes/sun/net/www/MessageHeader.java b/src/java.base/share/classes/sun/net/www/MessageHeader.java
index 95b82cda283..22b16407dd2 100644
--- a/src/java.base/share/classes/sun/net/www/MessageHeader.java
+++ b/src/java.base/share/classes/sun/net/www/MessageHeader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2020, 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
@@ -294,7 +294,7 @@ class MessageHeader {
      * @param line the line to check.
      * @return true if the line might be a request line.
      */
-    private boolean isRequestline(String line) {
+    private static boolean isRequestline(String line) {
         String k = line.trim();
         int i = k.lastIndexOf(' ');
         if (i <= 0) return false;
@@ -311,12 +311,23 @@ class MessageHeader {
         return (k.substring(i+1, len-3).equalsIgnoreCase("HTTP/"));
     }
 
+    /** Prints the key-value pairs represented by this
+        header. Also prints the RFC required blank line
+        at the end. Omits pairs with a null key. Omits
+        colon if key-value pair is the requestline. */
+    public void print(PrintStream p) {
+        // no synchronization: use cloned arrays instead.
+        String[] k; String[] v; int n;
+        synchronized (this) { n = nkeys; k = keys.clone(); v = values.clone(); }
+        print(n, k, v, p);
+    }
+
 
     /** Prints the key-value pairs represented by this
         header. Also prints the RFC required blank line
         at the end. Omits pairs with a null key. Omits
         colon if key-value pair is the requestline. */
-    public synchronized void print(PrintStream p) {
+    private  static void print(int nkeys, String[] keys, String[] values, PrintStream p) {
         for (int i = 0; i < nkeys; i++)
             if (keys[i] != null) {
                 StringBuilder sb = new StringBuilder(keys[i]);
diff --git a/src/java.base/share/classes/sun/net/www/MeteredStream.java b/src/java.base/share/classes/sun/net/www/MeteredStream.java
index 08ff179457a..049b16c03c6 100644
--- a/src/java.base/share/classes/sun/net/www/MeteredStream.java
+++ b/src/java.base/share/classes/sun/net/www/MeteredStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2020, 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
@@ -25,9 +25,8 @@
 
 package sun.net.www;
 
-import java.net.URL;
-import java.util.*;
 import java.io.*;
+import java.util.concurrent.locks.ReentrantLock;
 import sun.net.ProgressSource;
 import sun.net.www.http.ChunkedInputStream;
 
@@ -44,6 +43,7 @@ public class MeteredStream extends FilterInputStream {
     protected long markedCount = 0;
     protected int markLimit = -1;
     protected ProgressSource pi;
+    private final ReentrantLock readLock = new ReentrantLock();
 
     public MeteredStream(InputStream is, ProgressSource pi, long expected)
     {
@@ -57,7 +57,9 @@ public class MeteredStream extends FilterInputStream {
         }
     }
 
-    private final void justRead(long n) throws IOException   {
+    private final void justRead(long n) throws IOException {
+        assert isLockHeldByCurrentThread();
+
         if (n == -1) {
 
             /*
@@ -99,6 +101,7 @@ public class MeteredStream extends FilterInputStream {
      * Returns true if the mark is valid, false otherwise
      */
     private boolean isMarked() {
+        assert isLockHeldByCurrentThread();
 
         if (markLimit < 0) {
             return false;
@@ -113,94 +116,130 @@ public class MeteredStream extends FilterInputStream {
         return true;
     }
 
-    public synchronized int read() throws java.io.IOException {
-        if (closed) {
-            return -1;
+    public int read() throws java.io.IOException {
+        lock();
+        try {
+            if (closed) return -1;
+            int c = in.read();
+            if (c != -1) {
+                justRead(1);
+            } else {
+                justRead(c);
+            }
+            return c;
+        } finally {
+            unlock();
         }
-        int c = in.read();
-        if (c != -1) {
-            justRead(1);
-        } else {
-            justRead(c);
-        }
-        return c;
     }
 
-    public synchronized int read(byte b[], int off, int len)
+    public int read(byte b[], int off, int len)
                 throws java.io.IOException {
-        if (closed) {
-            return -1;
+        lock();
+        try {
+            if (closed) return -1;
+
+            int n = in.read(b, off, len);
+            justRead(n);
+            return n;
+        } finally {
+            unlock();
         }
-        int n = in.read(b, off, len);
-        justRead(n);
-        return n;
     }
 
-    public synchronized long skip(long n) throws IOException {
+    public long skip(long n) throws IOException {
+        lock();
+        try {
+            // REMIND: what does skip do on EOF????
+            if (closed) return 0;
 
-        // REMIND: what does skip do on EOF????
-        if (closed) {
-            return 0;
+            if (in instanceof ChunkedInputStream) {
+                n = in.skip(n);
+            } else {
+                // just skip min(n, num_bytes_left)
+                long min = (n > expected - count) ? expected - count : n;
+                n = in.skip(min);
+            }
+            justRead(n);
+            return n;
+        } finally {
+            unlock();
         }
-
-        if (in instanceof ChunkedInputStream) {
-            n = in.skip(n);
-        }
-        else {
-            // just skip min(n, num_bytes_left)
-            long min = (n > expected - count) ? expected - count: n;
-            n = in.skip(min);
-        }
-        justRead(n);
-        return n;
     }
 
     public void close() throws IOException {
-        if (closed) {
-            return;
-        }
-        if (pi != null)
-            pi.finishTracking();
+        lock();
+        try {
+            if (closed) return;
+            if (pi != null)
+                pi.finishTracking();
 
-        closed = true;
-        in.close();
+            closed = true;
+            in.close();
+        } finally {
+            unlock();
+        }
     }
 
-    public synchronized int available() throws IOException {
-        return closed ? 0: in.available();
+    public int available() throws IOException {
+        lock();
+        try {
+            return closed ? 0 : in.available();
+        } finally {
+            unlock();
+        }
     }
 
-    public synchronized void mark(int readLimit) {
-        if (closed) {
-            return;
-        }
-        super.mark(readLimit);
+    public void mark(int readLimit) {
+        lock();
+        try {
+            if (closed) return;
+            super.mark(readLimit);
 
-        /*
-         * mark the count to restore upon reset
-         */
-        markedCount = count;
-        markLimit = readLimit;
+            /*
+             * mark the count to restore upon reset
+             */
+            markedCount = count;
+            markLimit = readLimit;
+        } finally {
+            unlock();
+        }
     }
 
-    public synchronized void reset() throws IOException {
-        if (closed) {
-            return;
-        }
+    public void reset() throws IOException {
+        lock();
+        try {
+            if (closed) return;
+            if (!isMarked()) {
+                throw new IOException("Resetting to an invalid mark");
+            }
 
-        if (!isMarked()) {
-            throw new IOException ("Resetting to an invalid mark");
+            count = markedCount;
+            super.reset();
+        } finally {
+            unlock();
         }
-
-        count = markedCount;
-        super.reset();
     }
 
     public boolean markSupported() {
-        if (closed) {
-            return false;
+        lock();
+        try {
+            if (closed) return false;
+            return super.markSupported();
+        } finally {
+            unlock();
         }
-        return super.markSupported();
+    }
+
+    public final void lock() {
+        readLock.lock();
+    }
+
+    public final void unlock() {
+        readLock.unlock();
+    }
+
+    public final boolean isLockHeldByCurrentThread() {
+        return readLock.isHeldByCurrentThread();
     }
 
     @SuppressWarnings("deprecation")
diff --git a/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java b/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java
index 2c3d8ab2490..63dea6406dc 100644
--- a/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java
+++ b/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2020, 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
@@ -25,9 +25,7 @@
 package sun.net.www.http;
 
 import java.io.*;
-import java.util.*;
-
-import sun.net.*;
+import java.util.concurrent.locks.ReentrantLock;
 import sun.net.www.*;
 import sun.nio.cs.US_ASCII;
 
@@ -41,8 +39,7 @@ import sun.nio.cs.US_ASCII;
  * can be hurried to the end of the stream if the bytes are available on
  * the underlying stream.
  */
-public
-class ChunkedInputStream extends InputStream implements Hurryable {
+public class ChunkedInputStream extends InputStream implements Hurryable {
 
     /**
      * The underlying stream
@@ -126,6 +123,8 @@ class ChunkedInputStream extends InputStream implements Hurryable {
      */
     private boolean closed;
 
+    private final ReentrantLock readLock = new ReentrantLock();
+
     /*
      * Maximum chunk header size of 2KB + 2 bytes for CRLF
      */
@@ -648,14 +647,19 @@ class ChunkedInputStream extends InputStream implements Hurryable {
      * @exception  IOException  if an I/O error occurs.
      * @see        java.io.FilterInputStream#in
      */
-    public synchronized int read() throws IOException {
-        ensureOpen();
-        if (chunkPos >= chunkCount) {
-            if (readAhead(true) <= 0) {
-                return -1;
+    public int read() throws IOException {
+        readLock.lock();
+        try {
+            ensureOpen();
+            if (chunkPos >= chunkCount) {
+                if (readAhead(true) <= 0) {
+                    return -1;
+                }
             }
+            return chunkData[chunkPos++] & 0xff;
+        } finally {
+            readLock.unlock();
         }
-        return chunkData[chunkPos++] & 0xff;
     }
 
 
@@ -670,42 +674,47 @@ class ChunkedInputStream extends InputStream implements Hurryable {
      *             the stream has been reached.
      * @exception  IOException  if an I/O error occurs.
      */
-    public synchronized int read(byte b[], int off, int len)
+    public int read(byte b[], int off, int len)
         throws IOException
     {
-        ensureOpen();
-        if ((off < 0) || (off > b.length) || (len < 0) ||
-            ((off + len) > b.length) || ((off + len) < 0)) {
-            throw new IndexOutOfBoundsException();
-        } else if (len == 0) {
-            return 0;
-        }
-
-        int avail = chunkCount - chunkPos;
-        if (avail <= 0) {
-            /*
-             * Optimization: if we're in the middle of the chunk read
-             * directly from the underlying stream into the caller's
-             * buffer
-             */
-            if (state == STATE_READING_CHUNK) {
-                return fastRead( b, off, len );
+        readLock.lock();
+        try {
+            ensureOpen();
+            if ((off < 0) || (off > b.length) || (len < 0) ||
+                    ((off + len) > b.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            } else if (len == 0) {
+                return 0;
             }
 
-            /*
-             * We're not in the middle of a chunk so we must read ahead
-             * until there is some chunk data available.
-             */
-            avail = readAhead(true);
-            if (avail < 0) {
-                return -1;      /* EOF */
-            }
-        }
-        int cnt = (avail < len) ? avail : len;
-        System.arraycopy(chunkData, chunkPos, b, off, cnt);
-        chunkPos += cnt;
+            int avail = chunkCount - chunkPos;
+            if (avail <= 0) {
+                /*
+                 * Optimization: if we're in the middle of the chunk read
+                 * directly from the underlying stream into the caller's
+                 * buffer
+                 */
+                if (state == STATE_READING_CHUNK) {
+                    return fastRead(b, off, len);
+                }
 
-        return cnt;
+                /*
+                 * We're not in the middle of a chunk so we must read ahead
+                 * until there is some chunk data available.
+                 */
+                avail = readAhead(true);
+                if (avail < 0) {
+                    return -1;      /* EOF */
+                }
+            }
+            int cnt = (avail < len) ? avail : len;
+            System.arraycopy(chunkData, chunkPos, b, off, cnt);
+            chunkPos += cnt;
+
+            return cnt;
+        } finally {
+            readLock.unlock();
+        }
     }
 
     /**
@@ -717,20 +726,25 @@ class ChunkedInputStream extends InputStream implements Hurryable {
      * @exception  IOException  if an I/O error occurs.
      * @see        java.io.FilterInputStream#in
      */
-    public synchronized int available() throws IOException {
-        ensureOpen();
+    public int available() throws IOException {
+        readLock.lock();
+        try {
+            ensureOpen();
 
-        int avail = chunkCount - chunkPos;
-        if(avail > 0) {
-            return avail;
-        }
+            int avail = chunkCount - chunkPos;
+            if (avail > 0) {
+                return avail;
+            }
 
-        avail = readAhead(false);
+            avail = readAhead(false);
 
-        if (avail < 0) {
-            return 0;
-        } else  {
-            return avail;
+            if (avail < 0) {
+                return 0;
+            } else {
+                return avail;
+            }
+        } finally {
+            readLock.unlock();
         }
     }
 
@@ -745,12 +759,18 @@ class ChunkedInputStream extends InputStream implements Hurryable {
      *
      * @exception  IOException  if an I/O error occurs.
      */
-    public synchronized void close() throws IOException {
-        if (closed) {
-            return;
+    public void close() throws IOException {
+        if (closed) return;
+        readLock.lock();
+        try {
+            if (closed) {
+                return;
+            }
+            closeUnderlying();
+            closed = true;
+        } finally {
+            readLock.unlock();
         }
-        closeUnderlying();
-        closed = true;
     }
 
     /**
@@ -762,22 +782,27 @@ class ChunkedInputStream extends InputStream implements Hurryable {
      * without blocking then this stream can't be hurried and should be
      * closed.
      */
-    public synchronized boolean hurry() {
-        if (in == null || error) {
-            return false;
-        }
-
+    public boolean hurry() {
+        readLock.lock();
         try {
-            readAhead(false);
-        } catch (Exception e) {
-            return false;
-        }
+            if (in == null || error) {
+                return false;
+            }
 
-        if (error) {
-            return false;
-        }
+            try {
+                readAhead(false);
+            } catch (Exception e) {
+                return false;
+            }
 
-        return (state == STATE_DONE);
+            if (error) {
+                return false;
+            }
+
+            return (state == STATE_DONE);
+        } finally {
+            readLock.unlock();
+        }
     }
 
 }
diff --git a/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java b/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java
index f21bfbf6f65..4cf485cc33e 100644
--- a/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java
+++ b/src/java.base/share/classes/sun/net/www/http/ChunkedOutputStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2020, 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
@@ -25,6 +25,8 @@
 package sun.net.www.http;
 
 import java.io.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import sun.nio.cs.US_ASCII;
 
@@ -32,7 +34,7 @@ import sun.nio.cs.US_ASCII;
  * OutputStream that sends the output to the underlying stream using chunked
  * encoding as specified in RFC 2068.
  */
-public class ChunkedOutputStream extends PrintStream {
+public class ChunkedOutputStream extends OutputStream {
 
     /* Default chunk size (including chunk header) if not specified */
     static final int DEFAULT_CHUNK_SIZE = 4096;
@@ -63,6 +65,8 @@ public class ChunkedOutputStream extends PrintStream {
     /* header for a complete Chunk */
     private byte[] completeHeader;
 
+    private final Lock writeLock = new ReentrantLock();
+
     /* return the size of the header for a particular chunk size */
     private static int getHeaderSize(int size) {
         return (Integer.toHexString(size)).length() + CRLF_SIZE;
@@ -85,7 +89,6 @@ public class ChunkedOutputStream extends PrintStream {
     }
 
     public ChunkedOutputStream(PrintStream o, int size) {
-        super(o);
         out = o;
 
         if (size <= 0) {
@@ -144,7 +147,7 @@ public class ChunkedOutputStream extends PrintStream {
             reset();
         } else if (flushAll){
             /* complete the last chunk and flush it to underlying stream */
-            if (size > 0){
+            if (size > 0) {
                 /* adjust a header start index in case the header of the last
                  * chunk is shorter then preferedHeaderSize */
 
@@ -168,18 +171,18 @@ public class ChunkedOutputStream extends PrintStream {
 
             out.flush();
             reset();
-         }
+        }
     }
 
-    @Override
     public boolean checkError() {
-        return out.checkError();
+        var out = this.out;
+        return out == null || out.checkError();
     }
 
     /* Check that the output stream is still open */
-    private void ensureOpen() {
+    private void ensureOpen() throws IOException {
         if (out == null)
-            setError();
+            throw new IOException("closed");
     }
 
    /*
@@ -194,77 +197,92 @@ public class ChunkedOutputStream extends PrintStream {
     * The size of the data is of course smaller than preferredChunkSize.
     */
     @Override
-    public synchronized void write(byte b[], int off, int len) {
-        ensureOpen();
-        if ((off < 0) || (off > b.length) || (len < 0) ||
-            ((off + len) > b.length) || ((off + len) < 0)) {
-            throw new IndexOutOfBoundsException();
-        } else if (len == 0) {
-            return;
-        }
+    public void write(byte b[], int off, int len) throws IOException {
+        writeLock.lock();
+        try {
+            ensureOpen();
+            if ((off < 0) || (off > b.length) || (len < 0) ||
+                    ((off + len) > b.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            } else if (len == 0) {
+                return;
+            }
 
-        /* if b[] contains enough data then one loop cycle creates one complete
-         * data chunk with a header, body and a footer, and then flushes the
-         * chunk to the underlying stream. Otherwise, the last loop cycle
-         * creates incomplete data chunk with empty header and with no footer
-         * and stores this incomplete chunk in an internal buffer buf[]
-         */
-        int bytesToWrite = len;
-        int inputIndex = off;  /* the index of the byte[] currently being written */
+            /* if b[] contains enough data then one loop cycle creates one complete
+             * data chunk with a header, body and a footer, and then flushes the
+             * chunk to the underlying stream. Otherwise, the last loop cycle
+             * creates incomplete data chunk with empty header and with no footer
+             * and stores this incomplete chunk in an internal buffer buf[]
+             */
+            int bytesToWrite = len;
+            int inputIndex = off;  /* the index of the byte[] currently being written */
 
-        do {
-            /* enough data to complete a chunk */
-            if (bytesToWrite >= spaceInCurrentChunk) {
+            do {
+                /* enough data to complete a chunk */
+                if (bytesToWrite >= spaceInCurrentChunk) {
 
-                /* header */
-                for (int i=0; i<completeHeader.length; i++)
-                    buf[i] = completeHeader[i];
+                    /* header */
+                    for (int i = 0; i < completeHeader.length; i++)
+                        buf[i] = completeHeader[i];
 
-                /* data */
-                System.arraycopy(b, inputIndex, buf, count, spaceInCurrentChunk);
-                inputIndex += spaceInCurrentChunk;
-                bytesToWrite -= spaceInCurrentChunk;
-                count += spaceInCurrentChunk;
+                    /* data */
+                    System.arraycopy(b, inputIndex, buf, count, spaceInCurrentChunk);
+                    inputIndex += spaceInCurrentChunk;
+                    bytesToWrite -= spaceInCurrentChunk;
+                    count += spaceInCurrentChunk;
 
-                /* footer */
-                buf[count++] = FOOTER[0];
-                buf[count++] = FOOTER[1];
-                spaceInCurrentChunk = 0; //chunk is complete
+                    /* footer */
+                    buf[count++] = FOOTER[0];
+                    buf[count++] = FOOTER[1];
+                    spaceInCurrentChunk = 0; //chunk is complete
 
-                flush(false);
-                if (checkError()){
-                    break;
+                    flush(false);
+                    if (checkError()) {
+                        break;
+                    }
                 }
-            }
 
-            /* not enough data to build a chunk */
-            else {
-                /* header */
-                /* do not write header if not enough bytes to build a chunk yet */
+                /* not enough data to build a chunk */
+                else {
+                    /* header */
+                    /* do not write header if not enough bytes to build a chunk yet */
 
-                /* data */
-                System.arraycopy(b, inputIndex, buf, count, bytesToWrite);
-                count += bytesToWrite;
-                size += bytesToWrite;
-                spaceInCurrentChunk -= bytesToWrite;
-                bytesToWrite = 0;
+                    /* data */
+                    System.arraycopy(b, inputIndex, buf, count, bytesToWrite);
+                    count += bytesToWrite;
+                    size += bytesToWrite;
+                    spaceInCurrentChunk -= bytesToWrite;
+                    bytesToWrite = 0;
 
-                /* footer */
-                /* do not write header if not enough bytes to build a chunk yet */
-            }
-        } while (bytesToWrite > 0);
+                    /* footer */
+                    /* do not write header if not enough bytes to build a chunk yet */
+                }
+            } while (bytesToWrite > 0);
+        } finally {
+            writeLock.unlock();
+        }
     }
 
     @Override
-    public synchronized void write(int _b) {
-        byte b[] = {(byte)_b};
-        write(b, 0, 1);
+    public void write(int _b) throws IOException {
+        writeLock.lock();
+        try {
+            byte b[] = {(byte) _b};
+            write(b, 0, 1);
+        } finally {
+            writeLock.unlock();
+        }
     }
 
-    public synchronized void reset() {
-        count = preferedHeaderSize;
-        size = 0;
-        spaceInCurrentChunk = preferredChunkDataSize;
+    public void reset() {
+        writeLock.lock();
+        try {
+            count = preferedHeaderSize;
+            size = 0;
+            spaceInCurrentChunk = preferredChunkDataSize;
+        } finally {
+            writeLock.unlock();
+        }
     }
 
     public int size() {
@@ -272,26 +290,36 @@ public class ChunkedOutputStream extends PrintStream {
     }
 
     @Override
-    public synchronized void close() {
-        ensureOpen();
+    public void close() {
+        writeLock.lock();
+        try {
+           if (out == null) return;
 
-        /* if we have buffer a chunked send it */
-        if (size > 0) {
+            /* if we have buffer a chunked send it */
+            if (size > 0) {
+                flush(true);
+            }
+
+            /* send a zero length chunk */
             flush(true);
+
+            /* don't close the underlying stream */
+            out = null;
+        } finally {
+            writeLock.unlock();
         }
-
-        /* send a zero length chunk */
-        flush(true);
-
-        /* don't close the underlying stream */
-        out = null;
     }
 
     @Override
-    public synchronized void flush() {
-        ensureOpen();
-        if (size > 0) {
-            flush(true);
+    public void flush() throws IOException {
+        writeLock.lock();
+        try {
+            ensureOpen();
+            if (size > 0) {
+                flush(true);
+            }
+        } finally {
+            writeLock.unlock();
         }
     }
 }
diff --git a/src/java.base/share/classes/sun/net/www/http/HttpCapture.java b/src/java.base/share/classes/sun/net/www/http/HttpCapture.java
index 13ede95ac41..2ddd5289023 100644
--- a/src/java.base/share/classes/sun/net/www/http/HttpCapture.java
+++ b/src/java.base/share/classes/sun/net/www/http/HttpCapture.java
@@ -54,6 +54,8 @@ import sun.util.logging.PlatformLogger;
  * @author jccollet
  */
 public class HttpCapture {
+    // HttpCapture does blocking I/O operations while holding monitors.
+    // This is not a concern because it is rarely used.
     private File file;
     private boolean incoming = true;
     private BufferedWriter out;
diff --git a/src/java.base/share/classes/sun/net/www/http/HttpClient.java b/src/java.base/share/classes/sun/net/www/http/HttpClient.java
index dec1bfdfa1a..146fa4ff132 100644
--- a/src/java.base/share/classes/sun/net/www/http/HttpClient.java
+++ b/src/java.base/share/classes/sun/net/www/http/HttpClient.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2020, 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
@@ -30,6 +30,8 @@ import java.net.*;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Properties;
+import java.util.concurrent.locks.ReentrantLock;
+
 import sun.net.NetworkClient;
 import sun.net.ProgressSource;
 import sun.net.www.MessageHeader;
@@ -47,6 +49,8 @@ import sun.security.action.GetPropertyAction;
  * @author Dave Brown
  */
 public class HttpClient extends NetworkClient {
+    private final ReentrantLock clientLock = new ReentrantLock();
+
     // whether this httpclient comes from the cache
     protected boolean cachedHttpClient = false;
 
@@ -316,22 +320,28 @@ public class HttpClient extends NetworkClient {
                 boolean compatible = Objects.equals(ret.proxy, p)
                      && Objects.equals(ret.getAuthenticatorKey(), ak);
                 if (compatible) {
-                    synchronized (ret) {
+                    ret.lock();
+                    try {
                         ret.cachedHttpClient = true;
                         assert ret.inCache;
                         ret.inCache = false;
                         if (httpuc != null && ret.needsTunneling())
                             httpuc.setTunnelState(TUNNELING);
                         logFinest("KeepAlive stream retrieved from the cache, " + ret);
+                    } finally {
+                        ret.unlock();
                     }
                 } else {
                     // We cannot return this connection to the cache as it's
                     // KeepAliveTimeout will get reset. We simply close the connection.
                     // This should be fine as it is very rare that a connection
                     // to the same host will not use the same proxy.
-                    synchronized(ret) {
+                    ret.lock();
+                    try  {
                         ret.inCache = false;
                         ret.closeServer();
+                    } finally {
+                        ret.unlock();
                     }
                     ret = null;
                 }
@@ -409,10 +419,11 @@ public class HttpClient extends NetworkClient {
         }
     }
 
-    protected synchronized boolean available() {
+    protected boolean available() {
         boolean available = true;
         int old = -1;
 
+        lock();
         try {
             try {
                 old = serverSocket.getSoTimeout();
@@ -436,21 +447,33 @@ public class HttpClient extends NetworkClient {
             logFinest("HttpClient.available(): " +
                         "SocketException: not available");
             available = false;
+        } finally {
+            unlock();
         }
         return available;
     }
 
-    protected synchronized void putInKeepAliveCache() {
-        if (inCache) {
-            assert false : "Duplicate put to keep alive cache";
-            return;
+    protected void putInKeepAliveCache() {
+        lock();
+        try {
+            if (inCache) {
+                assert false : "Duplicate put to keep alive cache";
+                return;
+            }
+            inCache = true;
+            kac.put(url, null, this);
+        } finally {
+            unlock();
         }
-        inCache = true;
-        kac.put(url, null, this);
     }
 
-    protected synchronized boolean isInKeepAliveCache() {
-        return inCache;
+    protected boolean isInKeepAliveCache() {
+        lock();
+        try {
+            return inCache;
+        } finally {
+            unlock();
+        }
     }
 
     /*
@@ -497,8 +520,13 @@ public class HttpClient extends NetworkClient {
     /*
      * Returns true if this httpclient is from cache
      */
-    public synchronized boolean isCachedConnection() {
-        return cachedHttpClient;
+    public boolean isCachedConnection() {
+        lock();
+        try {
+            return cachedHttpClient;
+        } finally {
+            unlock();
+        }
     }
 
     /*
@@ -516,9 +544,10 @@ public class HttpClient extends NetworkClient {
     /*
      * call openServer in a privileged block
      */
-    private synchronized void privilegedOpenServer(final InetSocketAddress server)
+    private void privilegedOpenServer(final InetSocketAddress server)
          throws IOException
     {
+        assert clientLock.isHeldByCurrentThread();
         try {
             java.security.AccessController.doPrivileged(
                 new java.security.PrivilegedExceptionAction<>() {
@@ -544,48 +573,53 @@ public class HttpClient extends NetworkClient {
 
     /*
      */
-    protected synchronized void openServer() throws IOException {
+    protected void openServer() throws IOException {
 
         SecurityManager security = System.getSecurityManager();
 
-        if (security != null) {
-            security.checkConnect(host, port);
-        }
+        lock();
+        try {
+            if (security != null) {
+                security.checkConnect(host, port);
+            }
 
-        if (keepingAlive) { // already opened
-            return;
-        }
-
-        if (url.getProtocol().equals("http") ||
-            url.getProtocol().equals("https") ) {
-
-            if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
-                sun.net.www.URLConnection.setProxiedHost(host);
-                privilegedOpenServer((InetSocketAddress) proxy.address());
-                usingProxy = true;
-                return;
-            } else {
-                // make direct connection
-                openServer(host, port);
-                usingProxy = false;
+            if (keepingAlive) { // already opened
                 return;
             }
 
-        } else {
-            /* we're opening some other kind of url, most likely an
-             * ftp url.
-             */
-            if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
-                sun.net.www.URLConnection.setProxiedHost(host);
-                privilegedOpenServer((InetSocketAddress) proxy.address());
-                usingProxy = true;
-                return;
+            if (url.getProtocol().equals("http") ||
+                    url.getProtocol().equals("https")) {
+
+                if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
+                    sun.net.www.URLConnection.setProxiedHost(host);
+                    privilegedOpenServer((InetSocketAddress) proxy.address());
+                    usingProxy = true;
+                    return;
+                } else {
+                    // make direct connection
+                    openServer(host, port);
+                    usingProxy = false;
+                    return;
+                }
+
             } else {
-                // make direct connection
-                super.openServer(host, port);
-                usingProxy = false;
-                return;
+                /* we're opening some other kind of url, most likely an
+                 * ftp url.
+                 */
+                if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
+                    sun.net.www.URLConnection.setProxiedHost(host);
+                    privilegedOpenServer((InetSocketAddress) proxy.address());
+                    usingProxy = true;
+                    return;
+                } else {
+                    // make direct connection
+                    super.openServer(host, port);
+                    usingProxy = false;
+                    return;
+                }
             }
+        } finally {
+            unlock();
         }
     }
 
@@ -1010,8 +1044,13 @@ public class HttpClient extends NetworkClient {
         return ret;
     }
 
-    public synchronized InputStream getInputStream() {
-        return serverInput;
+    public InputStream getInputStream() {
+        lock();
+        try {
+            return serverInput;
+        } finally {
+            unlock();
+        }
     }
 
     public OutputStream getOutputStream() {
@@ -1084,4 +1123,12 @@ public class HttpClient extends NetworkClient {
             return ((InetSocketAddress)proxy.address()).getPort();
         return -1;
     }
+
+    public final void lock() {
+        clientLock.lock();
+    }
+
+    public final void unlock() {
+        clientLock.unlock();
+    }
 }
diff --git a/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java b/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
index d813561a167..66ebd51b52d 100644
--- a/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
+++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2020, 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
@@ -36,6 +36,8 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import jdk.internal.misc.InnocuousThread;
 import sun.security.action.GetIntegerAction;
@@ -74,6 +76,8 @@ public class KeepAliveCache
 
     static final int LIFETIME = 5000;
 
+    // This class is never serialized (see writeObject/readObject).
+    private final ReentrantLock cacheLock = new ReentrantLock();
     private Thread keepAliveTimer = null;
 
     /**
@@ -86,76 +90,92 @@ public class KeepAliveCache
      * @param url  The URL contains info about the host and port
      * @param http The HttpClient to be cached
      */
-    public synchronized void put(final URL url, Object obj, HttpClient http) {
-        boolean startThread = (keepAliveTimer == null);
-        if (!startThread) {
-            if (!keepAliveTimer.isAlive()) {
-                startThread = true;
-            }
-        }
-        if (startThread) {
-            clear();
-            /* Unfortunately, we can't always believe the keep-alive timeout we got
-             * back from the server.  If I'm connected through a Netscape proxy
-             * to a server that sent me a keep-alive
-             * time of 15 sec, the proxy unilaterally terminates my connection
-             * The robustness to get around this is in HttpClient.parseHTTP()
-             */
-            final KeepAliveCache cache = this;
-            AccessController.doPrivileged(new PrivilegedAction<>() {
-                public Void run() {
-                    keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
-                    keepAliveTimer.setDaemon(true);
-                    keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
-                    keepAliveTimer.start();
-                    return null;
+    public void put(final URL url, Object obj, HttpClient http) {
+        cacheLock.lock();
+        try {
+            boolean startThread = (keepAliveTimer == null);
+            if (!startThread) {
+                if (!keepAliveTimer.isAlive()) {
+                    startThread = true;
                 }
-            });
-        }
+            }
+            if (startThread) {
+                clear();
+                /* Unfortunately, we can't always believe the keep-alive timeout we got
+                 * back from the server.  If I'm connected through a Netscape proxy
+                 * to a server that sent me a keep-alive
+                 * time of 15 sec, the proxy unilaterally terminates my connection
+                 * The robustness to get around this is in HttpClient.parseHTTP()
+                 */
+                final KeepAliveCache cache = this;
+                AccessController.doPrivileged(new PrivilegedAction<>() {
+                    public Void run() {
+                        keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
+                        keepAliveTimer.setDaemon(true);
+                        keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
+                        keepAliveTimer.start();
+                        return null;
+                    }
+                });
+            }
 
-        KeepAliveKey key = new KeepAliveKey(url, obj);
-        ClientVector v = super.get(key);
+            KeepAliveKey key = new KeepAliveKey(url, obj);
+            ClientVector v = super.get(key);
 
-        if (v == null) {
-            int keepAliveTimeout = http.getKeepAliveTimeout();
-            v = new ClientVector(keepAliveTimeout > 0 ?
-                                 keepAliveTimeout * 1000 : LIFETIME);
-            v.put(http);
-            super.put(key, v);
-        } else {
-            v.put(http);
+            if (v == null) {
+                int keepAliveTimeout = http.getKeepAliveTimeout();
+                v = new ClientVector(keepAliveTimeout > 0 ?
+                        keepAliveTimeout * 1000 : LIFETIME);
+                v.put(http);
+                super.put(key, v);
+            } else {
+                v.put(http);
+            }
+        } finally {
+            cacheLock.unlock();
         }
     }
 
     /* remove an obsolete HttpClient from its VectorCache */
-    public synchronized void remove(HttpClient h, Object obj) {
-        KeepAliveKey key = new KeepAliveKey(h.url, obj);
-        ClientVector v = super.get(key);
-        if (v != null) {
-            v.remove(h);
-            if (v.isEmpty()) {
-                removeVector(key);
+    public void remove(HttpClient h, Object obj) {
+        cacheLock.lock();
+        try {
+            KeepAliveKey key = new KeepAliveKey(h.url, obj);
+            ClientVector v = super.get(key);
+            if (v != null) {
+                v.remove(h);
+                if (v.isEmpty()) {
+                    removeVector(key);
+                }
             }
+        } finally {
+            cacheLock.unlock();
         }
     }
 
     /* called by a clientVector thread when all its connections have timed out
      * and that vector of connections should be removed.
      */
-    synchronized void removeVector(KeepAliveKey k) {
+    private void removeVector(KeepAliveKey k) {
+        assert cacheLock.isHeldByCurrentThread();
         super.remove(k);
     }
 
     /**
      * Check to see if this URL has a cached HttpClient
      */
-    public synchronized HttpClient get(URL url, Object obj) {
-        KeepAliveKey key = new KeepAliveKey(url, obj);
-        ClientVector v = super.get(key);
-        if (v == null) { // nothing in cache yet
-            return null;
+    public HttpClient get(URL url, Object obj) {
+        cacheLock.lock();
+        try {
+            KeepAliveKey key = new KeepAliveKey(url, obj);
+            ClientVector v = super.get(key);
+            if (v == null) { // nothing in cache yet
+                return null;
+            }
+            return v.get();
+        } finally {
+            cacheLock.unlock();
         }
-        return v.get();
     }
 
     /* Sleeps for an alloted timeout, then checks for timed out connections.
@@ -170,13 +190,15 @@ public class KeepAliveCache
             } catch (InterruptedException e) {}
 
             // Remove all outdated HttpClients.
-            synchronized (this) {
+            cacheLock.lock();
+            try {
                 long currentTime = System.currentTimeMillis();
                 List<KeepAliveKey> keysToRemove = new ArrayList<>();
 
                 for (KeepAliveKey key : keySet()) {
                     ClientVector v = get(key);
-                    synchronized (v) {
+                    v.lock();
+                    try {
                         KeepAliveEntry e = v.peek();
                         while (e != null) {
                             if ((currentTime - e.idleStartTime) > v.nap) {
@@ -191,12 +213,16 @@ public class KeepAliveCache
                         if (v.isEmpty()) {
                             keysToRemove.add(key);
                         }
+                    } finally {
+                        v.unlock();
                     }
                 }
 
                 for (KeepAliveKey key : keysToRemove) {
                     removeVector(key);
                 }
+            } finally {
+                cacheLock.unlock();
             }
         } while (!isEmpty());
     }
@@ -223,6 +249,7 @@ public class KeepAliveCache
 class ClientVector extends ArrayDeque<KeepAliveEntry> {
     @java.io.Serial
     private static final long serialVersionUID = -8680532108106489459L;
+    private final ReentrantLock lock = new ReentrantLock();
 
     // sleep time in milliseconds, before cache clear
     int nap;
@@ -231,42 +258,65 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
         this.nap = nap;
     }
 
-    synchronized HttpClient get() {
-        if (isEmpty()) {
-            return null;
-        }
-
-        // Loop until we find a connection that has not timed out
-        HttpClient hc = null;
-        long currentTime = System.currentTimeMillis();
-        do {
-            KeepAliveEntry e = pop();
-            if ((currentTime - e.idleStartTime) > nap) {
-                e.hc.closeServer();
-            } else {
-                hc = e.hc;
+    HttpClient get() {
+        lock();
+        try {
+            if (isEmpty()) {
+                return null;
             }
-        } while ((hc == null) && (!isEmpty()));
-        return hc;
+
+            // Loop until we find a connection that has not timed out
+            HttpClient hc = null;
+            long currentTime = System.currentTimeMillis();
+            do {
+                KeepAliveEntry e = pop();
+                if ((currentTime - e.idleStartTime) > nap) {
+                    e.hc.closeServer();
+                } else {
+                    hc = e.hc;
+                }
+            } while ((hc == null) && (!isEmpty()));
+            return hc;
+        } finally {
+            unlock();
+        }
     }
 
     /* return a still valid, unused HttpClient */
-    synchronized void put(HttpClient h) {
-        if (size() >= KeepAliveCache.getMaxConnections()) {
-            h.closeServer(); // otherwise the connection remains in limbo
-        } else {
-            push(new KeepAliveEntry(h, System.currentTimeMillis()));
+    void put(HttpClient h) {
+        lock();
+        try {
+            if (size() >= KeepAliveCache.getMaxConnections()) {
+                h.closeServer(); // otherwise the connection remains in limbo
+            } else {
+                push(new KeepAliveEntry(h, System.currentTimeMillis()));
+            }
+        } finally {
+            unlock();
         }
     }
 
     /* remove an HttpClient */
-    synchronized boolean remove(HttpClient h) {
-        for (KeepAliveEntry curr : this) {
-            if (curr.hc == h) {
-                return super.remove(curr);
+    boolean remove(HttpClient h) {
+        lock();
+        try {
+            for (KeepAliveEntry curr : this) {
+                if (curr.hc == h) {
+                    return super.remove(curr);
+                }
             }
+            return false;
+        } finally {
+            unlock();
         }
-        return false;
+    }
+
+    final void lock() {
+        lock.lock();
+    }
+
+    final void unlock() {
+        lock.unlock();
     }
 
     /*
diff --git a/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java b/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java
index 3af7c555a5a..7963e4c856f 100644
--- a/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java
+++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveStream.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2020, 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
@@ -47,7 +47,8 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
     boolean hurried;
 
     // has this KeepAliveStream been put on the queue for asynchronous cleanup.
-    protected boolean queuedForCleanup = false;
+    // This flag is read from within KeepAliveCleanerEntry outside of any lock.
+    protected volatile boolean queuedForCleanup = false;
 
     private static final KeepAliveStreamCleaner queue = new KeepAliveStreamCleaner();
     private static Thread cleanerThread; // null
@@ -64,15 +65,8 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
      * Attempt to cache this connection
      */
     public void close() throws IOException  {
-        // If the inputstream is closed already, just return.
-        if (closed) {
-            return;
-        }
-
-        // If this stream has already been queued for cleanup.
-        if (queuedForCleanup) {
-            return;
-        }
+        // If the inputstream is queued for cleanup, just return.
+        if (queuedForCleanup) return;
 
         // Skip past the data that's left in the Inputstream because
         // some sort of error may have occurred.
@@ -81,34 +75,45 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
         // to hang around for nothing. So if we can't skip without blocking
         // we just close the socket and, therefore, terminate the keepAlive
         // NOTE: Don't close super class
+        // For consistency, access to `expected` and `count` should be
+        // protected by readLock
+        lock();
         try {
-            if (expected > count) {
-                long nskip = expected - count;
-                if (nskip <= available()) {
-                    do {} while ((nskip = (expected - count)) > 0L
-                                 && skip(Math.min(nskip, available())) > 0L);
-                } else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) {
-                    //put this KeepAliveStream on the queue so that the data remaining
-                    //on the socket can be cleanup asyncronously.
-                    queueForCleanup(new KeepAliveCleanerEntry(this, hc));
-                } else {
-                    hc.closeServer();
+            // If the inputstream is closed already, or if this stream
+            // has already been queued for cleanup, just return.
+            if (closed || queuedForCleanup) return;
+            try {
+                if (expected > count) {
+                    long nskip = expected - count;
+                    if (nskip <= available()) {
+                        do {
+                        } while ((nskip = (expected - count)) > 0L
+                                && skip(Math.min(nskip, available())) > 0L);
+                    } else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) {
+                        //put this KeepAliveStream on the queue so that the data remaining
+                        //on the socket can be cleanup asyncronously.
+                        queueForCleanup(new KeepAliveCleanerEntry(this, hc));
+                    } else {
+                        hc.closeServer();
+                    }
+                }
+                if (!closed && !hurried && !queuedForCleanup) {
+                    hc.finished();
+                }
+            } finally {
+                if (pi != null)
+                    pi.finishTracking();
+
+                if (!queuedForCleanup) {
+                    // nulling out the underlying inputstream as well as
+                    // httpClient to let gc collect the memories faster
+                    in = null;
+                    hc = null;
+                    closed = true;
                 }
             }
-            if (!closed && !hurried && !queuedForCleanup) {
-                hc.finished();
-            }
         } finally {
-            if (pi != null)
-                pi.finishTracking();
-
-            if (!queuedForCleanup) {
-                // nulling out the underlying inputstream as well as
-                // httpClient to let gc collect the memories faster
-                in = null;
-                hc = null;
-                closed = true;
-            }
+            unlock();
         }
     }
 
@@ -124,7 +129,8 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
         throw new IOException("mark/reset not supported");
     }
 
-    public synchronized boolean hurry() {
+    public boolean hurry() {
+        lock();
         try {
             /* CASE 0: we're actually already done */
             if (closed || count >= expected) {
@@ -147,11 +153,14 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
         } catch (IOException e) {
             // e.printStackTrace();
             return false;
+        } finally {
+            unlock();
         }
     }
 
     private static void queueForCleanup(KeepAliveCleanerEntry kace) {
-        synchronized(queue) {
+        queue.lock();
+        try {
             if(!kace.getQueuedForCleanup()) {
                 if (!queue.offer(kace)) {
                     kace.getHttpClient().closeServer();
@@ -159,7 +168,7 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
                 }
 
                 kace.setQueuedForCleanup();
-                queue.notifyAll();
+                queue.signalAll();
             }
 
             boolean startCleanupThread = (cleanerThread == null);
@@ -181,14 +190,20 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
                     }
                 });
             }
-        } // queue
+        } finally {
+            queue.unlock();
+        }
     }
 
+    // Only called from KeepAliveStreamCleaner
     protected long remainingToRead() {
+        assert isLockHeldByCurrentThread();
         return expected - count;
     }
 
+    // Only called from KeepAliveStreamCleaner
     protected void setClosed() {
+        assert isLockHeldByCurrentThread();
         in = null;
         hc = null;
         closed = true;
diff --git a/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java b/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java
index 87f0d09ea62..5ad38485453 100644
--- a/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java
+++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2020, 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
@@ -30,6 +30,8 @@ import java.util.LinkedList;
 import sun.net.NetProperties;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * This class is used to cleanup any remaining data that may be on a KeepAliveStream
@@ -78,6 +80,20 @@ class KeepAliveStreamCleaner
 
     }
 
+    private final ReentrantLock queueLock = new ReentrantLock();
+    private final Condition waiter = queueLock.newCondition();
+
+    final void signalAll() {
+        waiter.signalAll();
+    }
+
+    final void lock() {
+        queueLock.lock();
+    }
+
+    final void unlock() {
+        queueLock.unlock();
+    }
 
     @Override
     public boolean offer(KeepAliveCleanerEntry e) {
@@ -94,11 +110,12 @@ class KeepAliveStreamCleaner
 
         do {
             try {
-                synchronized(this) {
+                lock();
+                try {
                     long before = System.currentTimeMillis();
                     long timeout = TIMEOUT;
                     while ((kace = poll()) == null) {
-                        this.wait(timeout);
+                        waiter.wait(timeout);
 
                         long after = System.currentTimeMillis();
                         long elapsed = after - before;
@@ -110,6 +127,8 @@ class KeepAliveStreamCleaner
                         before = after;
                         timeout -= elapsed;
                     }
+                } finally {
+                    unlock();
                 }
 
                 if(kace == null)
@@ -118,7 +137,8 @@ class KeepAliveStreamCleaner
                 KeepAliveStream kas = kace.getKeepAliveStream();
 
                 if (kas != null) {
-                    synchronized(kas) {
+                    kas.lock();
+                    try {
                         HttpClient hc = kace.getHttpClient();
                         try {
                             if (hc != null && !hc.isInKeepAliveCache()) {
@@ -147,6 +167,8 @@ class KeepAliveStreamCleaner
                         } finally {
                             kas.setClosed();
                         }
+                    } finally {
+                        kas.unlock();
                     }
                 }
             } catch (InterruptedException ie) { }
diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java b/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java
index 45a5a0eaf9b..a35dfc17ebb 100644
--- a/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java
+++ b/src/java.base/share/classes/sun/net/www/protocol/http/AuthCacheImpl.java
@@ -32,8 +32,10 @@ import java.util.HashMap;
 /**
  * @author Michael McMahon
  */
-
 public class AuthCacheImpl implements AuthCache {
+    // No blocking IO is performed within the synchronized code blocks
+    // in this class, so there is no need to convert this class to using
+    // java.util.concurrent.locks
     HashMap<String,LinkedList<AuthCacheValue>> hashtable;
 
     public AuthCacheImpl () {
@@ -46,7 +48,6 @@ public class AuthCacheImpl implements AuthCache {
 
     // put a value in map according to primary key + secondary key which
     // is the path field of AuthenticationInfo
-
     public synchronized void put (String pkey, AuthCacheValue value) {
         LinkedList<AuthCacheValue> list = hashtable.get (pkey);
         String skey = value.getPath();
diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java b/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java
index bbf66dfd1c0..3f0f33da511 100644
--- a/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java
+++ b/src/java.base/share/classes/sun/net/www/protocol/http/AuthenticationInfo.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2020, 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
@@ -31,6 +31,8 @@ import java.net.PasswordAuthentication;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Function;
 
 import sun.net.www.HeaderParser;
@@ -125,8 +127,9 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
      * at the same time, then all but the first will block until
      * the first completes its authentication.
      */
-    private static HashMap<String,Thread> requests = new HashMap<>();
-
+    private static final HashMap<String,Thread> requests = new HashMap<>();
+    private static final ReentrantLock requestLock = new ReentrantLock();
+    private static final Condition requestFinished = requestLock.newCondition();
     /*
      * check if AuthenticationInfo is available in the cache.
      * If not, check if a request for this destination is in progress
@@ -142,8 +145,9 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
             // and we can revert to concurrent requests
             return cached;
         }
-        synchronized (requests) {
-            // check again after synchronizing, and if available
+        requestLock.lock();
+        try {
+            // check again after locking, and if available
             // just return the cached value.
             cached = cache.apply(key);
             if (cached != null) return cached;
@@ -164,10 +168,10 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
             // Otherwise, an other thread is currently performing authentication:
             // wait until it finishes.
             while (requests.containsKey(key)) {
-                try {
-                    requests.wait ();
-                } catch (InterruptedException e) {}
+                requestFinished.awaitUninterruptibly();
             }
+        } finally {
+            requestLock.unlock();
         }
         /* entry may be in cache now. */
         return cache.apply(key);
@@ -177,13 +181,16 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
      * so that other threads can continue.
      */
     private static void requestCompleted (String key) {
-        synchronized (requests) {
+        requestLock.lock();
+        try {
             Thread thread = requests.get(key);
             if (thread != null && thread == Thread.currentThread()) {
                 boolean waspresent = requests.remove(key) != null;
                 assert waspresent;
             }
-            requests.notifyAll();
+            requestFinished.signalAll();
+        } finally {
+            requestLock.unlock();
         }
     }
 
@@ -414,9 +421,7 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
         if (!serializeAuth) {
             return;
         }
-        synchronized (requests) {
-            requestCompleted(key);
-        }
+        requestCompleted(key);
     }
 
     /**
@@ -500,6 +505,7 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
     String s1, s2;  /* used for serialization of pw */
 
     @java.io.Serial
+    // should be safe to keep synchronized here
     private synchronized void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException
     {
@@ -512,6 +518,7 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
     }
 
     @java.io.Serial
+    // should be safe to keep synchronized here
     private synchronized void writeObject(java.io.ObjectOutputStream s)
         throws IOException
     {
diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.java b/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.java
index 67cd90cf455..4dce36840c4 100644
--- a/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.java
+++ b/src/java.base/share/classes/sun/net/www/protocol/http/BasicAuthentication.java
@@ -141,6 +141,9 @@ class BasicAuthentication extends AuthenticationInfo {
      */
     @Override
     public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+        // no need to synchronize here:
+        //   already locked by s.n.w.p.h.HttpURLConnection
+        assert conn.isLockHeldByCurrentThread();
         conn.setAuthenticationProperty(getHeaderName(), getHeaderValue(null,null));
         return true;
     }
diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java b/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java
index cf81dbfaecd..0f3ccdb56e6 100644
--- a/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java
+++ b/src/java.base/share/classes/sun/net/www/protocol/http/DigestAuthentication.java
@@ -78,6 +78,9 @@ class DigestAuthentication extends AuthenticationInfo {
     // One instance of these may be shared among several DigestAuthentication
     // instances as a result of a single authorization (for multiple domains)
 
+    // There don't appear to be any blocking IO calls performed from
+    // within the synchronized code blocks in the Parameters class, so there don't
+    // seem to be any need to migrate it to using java.util.concurrent.locks
     static class Parameters implements java.io.Serializable {
         private static final long serialVersionUID = -3584543755194526252L;
 
@@ -298,6 +301,10 @@ class DigestAuthentication extends AuthenticationInfo {
      */
     @Override
     public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+        // no need to synchronize here:
+        //   already locked by s.n.w.p.h.HttpURLConnection
+        assert conn.isLockHeldByCurrentThread();
+
         params.setNonce (p.findValue("nonce"));
         params.setOpaque (p.findValue("opaque"));
         params.setQop (p.findValue("qop"));
diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java
index 96ce9e9e604..ce30e75011c 100644
--- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java
+++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java
@@ -81,6 +81,8 @@ import java.net.MalformedURLException;
 import java.nio.ByteBuffer;
 import java.util.Objects;
 import java.util.Properties;
+import java.util.concurrent.locks.ReentrantLock;
+
 import static sun.net.www.protocol.http.AuthScheme.BASIC;
 import static sun.net.www.protocol.http.AuthScheme.DIGEST;
 import static sun.net.www.protocol.http.AuthScheme.NTLM;
@@ -97,7 +99,7 @@ import sun.security.action.GetPropertyAction;
 
 public class HttpURLConnection extends java.net.HttpURLConnection {
 
-    static String HTTP_CONNECT = "CONNECT";
+    static final String HTTP_CONNECT = "CONNECT";
 
     static final String version;
     public static final String userAgent;
@@ -353,7 +355,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      *     - getOutputStream()
      *     - getInputStream())
      *     - connect()
-     * Access synchronized on this.
+     * Access is protected by connectionLock.
      */
     private boolean connecting = false;
 
@@ -432,6 +434,22 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     private static final PlatformLogger logger =
             PlatformLogger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
 
+    /* Lock */
+    private final ReentrantLock connectionLock = new ReentrantLock();
+
+    private final void lock() {
+        connectionLock.lock();
+    }
+
+    private final void unlock() {
+        connectionLock.unlock();
+    }
+
+    public final boolean isLockHeldByCurrentThread() {
+        return connectionLock.isHeldByCurrentThread();
+    }
+
+
     /*
      * privileged request password authentication
      *
@@ -514,13 +532,18 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     }
 
     @Override
-    public synchronized void setAuthenticator(Authenticator auth) {
-        if (connecting || connected) {
-            throw new IllegalStateException(
-                  "Authenticator must be set before connecting");
+    public void setAuthenticator(Authenticator auth) {
+        lock();
+        try {
+            if (connecting || connected) {
+                throw new IllegalStateException(
+                        "Authenticator must be set before connecting");
+            }
+            authenticator = Objects.requireNonNull(auth);
+            authenticatorKey = AuthenticatorKeys.getKey(authenticator);
+        } finally {
+            unlock();
         }
-        authenticator = Objects.requireNonNull(auth);
-        authenticatorKey = AuthenticatorKeys.getKey(authenticator);
     }
 
     public String getAuthenticatorKey() {
@@ -563,18 +586,25 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
         }
     }
 
-    public synchronized void setRequestMethod(String method)
+    public void setRequestMethod(String method)
                         throws ProtocolException {
-        if (connecting) {
-            throw new IllegalStateException("connect in progress");
+        lock();
+        try {
+            if (connecting) {
+                throw new IllegalStateException("connect in progress");
+            }
+            super.setRequestMethod(method);
+        } finally {
+            unlock();
         }
-        super.setRequestMethod(method);
     }
 
     /* adds the standard key/val pairs to reqests if necessary & write to
      * given PrintStream
      */
     private void writeRequests() throws IOException {
+        assert isLockHeldByCurrentThread();
+
         /* print all message headers in the MessageHeader
          * onto the wire - all the ones we've set and any
          * others that have been set
@@ -683,6 +713,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
                 }
             } else if (poster != null) {
                 /* add Content-Length & POST/PUT data */
+                // safe to synchronize on poster: this is
+                // a simple subclass of ByteArrayOutputStream
                 synchronized (poster) {
                     /* close it, so no more data can be added */
                     poster.close();
@@ -1010,8 +1042,11 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     // overridden in HTTPS subclass
 
     public void connect() throws IOException {
-        synchronized (this) {
+        lock();
+        try {
             connecting = true;
+        } finally {
+            unlock();
         }
         plainConnect();
     }
@@ -1057,11 +1092,14 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
         return host + ":" + Integer.toString(port);
     }
 
-    protected void plainConnect()  throws IOException {
-        synchronized (this) {
+    protected void plainConnect() throws IOException {
+        lock();
+        try {
             if (connected) {
                 return;
             }
+        } finally {
+            unlock();
         }
         SocketPermission p = URLtoSocketPermission(this.url);
         if (p != null) {
@@ -1330,28 +1368,34 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      */
 
     @Override
-    public synchronized OutputStream getOutputStream() throws IOException {
-        connecting = true;
-        SocketPermission p = URLtoSocketPermission(this.url);
+    public OutputStream getOutputStream() throws IOException {
+        lock();
+        try {
+            connecting = true;
+            SocketPermission p = URLtoSocketPermission(this.url);
 
-        if (p != null) {
-            try {
-                return AccessController.doPrivilegedWithCombiner(
-                    new PrivilegedExceptionAction<>() {
-                        public OutputStream run() throws IOException {
-                            return getOutputStream0();
-                        }
-                    }, null, p
-                );
-            } catch (PrivilegedActionException e) {
-                throw (IOException) e.getException();
+            if (p != null) {
+                try {
+                    return AccessController.doPrivilegedWithCombiner(
+                            new PrivilegedExceptionAction<>() {
+                                public OutputStream run() throws IOException {
+                                    return getOutputStream0();
+                                }
+                            }, null, p
+                    );
+                } catch (PrivilegedActionException e) {
+                    throw (IOException) e.getException();
+                }
+            } else {
+                return getOutputStream0();
             }
-        } else {
-            return getOutputStream0();
+        } finally {
+            unlock();
         }
     }
 
-    private synchronized OutputStream getOutputStream0() throws IOException {
+    private OutputStream getOutputStream0() throws IOException {
+        assert isLockHeldByCurrentThread();
         try {
             if (!doOutput) {
                 throw new ProtocolException("cannot write to a URLConnection"
@@ -1441,16 +1485,19 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
             // we only want to capture the user defined Cookies once, as
             // they cannot be changed by user code after we are connected,
             // only internally.
-            synchronized (this) {
-                if (setUserCookies) {
-                    int k = requests.getKey("Cookie");
-                    if (k != -1)
-                        userCookies = requests.getValue(k);
-                    k = requests.getKey("Cookie2");
-                    if (k != -1)
-                        userCookies2 = requests.getValue(k);
-                    setUserCookies = false;
-                }
+
+            // we should only reach here when called from
+            // writeRequest, which in turn is only called by
+            // getInputStream0
+            assert isLockHeldByCurrentThread();
+            if (setUserCookies) {
+                int k = requests.getKey("Cookie");
+                if (k != -1)
+                    userCookies = requests.getValue(k);
+                k = requests.getKey("Cookie2");
+                if (k != -1)
+                    userCookies2 = requests.getValue(k);
+                setUserCookies = false;
             }
 
             // remove old Cookie header before setting new one.
@@ -1508,30 +1555,36 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     }
 
     @Override
-    public synchronized InputStream getInputStream() throws IOException {
-        connecting = true;
-        SocketPermission p = URLtoSocketPermission(this.url);
+    public InputStream getInputStream() throws IOException {
+        lock();
+        try {
+            connecting = true;
+            SocketPermission p = URLtoSocketPermission(this.url);
 
-        if (p != null) {
-            try {
-                return AccessController.doPrivilegedWithCombiner(
-                    new PrivilegedExceptionAction<>() {
-                        public InputStream run() throws IOException {
-                            return getInputStream0();
-                        }
-                    }, null, p
-                );
-            } catch (PrivilegedActionException e) {
-                throw (IOException) e.getException();
+            if (p != null) {
+                try {
+                    return AccessController.doPrivilegedWithCombiner(
+                            new PrivilegedExceptionAction<>() {
+                                public InputStream run() throws IOException {
+                                    return getInputStream0();
+                                }
+                            }, null, p
+                    );
+                } catch (PrivilegedActionException e) {
+                    throw (IOException) e.getException();
+                }
+            } else {
+                return getInputStream0();
             }
-        } else {
-            return getInputStream0();
+        } finally {
+            unlock();
         }
     }
 
     @SuppressWarnings("empty-statement")
-    private synchronized InputStream getInputStream0() throws IOException {
+    private InputStream getInputStream0() throws IOException {
 
+        assert isLockHeldByCurrentThread();
         if (!doInput) {
             throw new ProtocolException("Cannot read from URLConnection"
                    + " if doInput=false (call setDoInput(true))");
@@ -2010,6 +2063,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      */
     private AuthenticationInfo
         resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) throws IOException {
+
+        // Only called from getInputStream0 and doTunneling0
+        assert isLockHeldByCurrentThread();
+
         if ((proxyAuthentication != null )&&
              proxyAuthentication.getAuthScheme() != NTLM) {
             String raw = auth.raw();
@@ -2060,7 +2117,16 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     /**
      * establish a tunnel through proxy server
      */
-    public synchronized void doTunneling() throws IOException {
+    public void doTunneling() throws IOException {
+        lock();
+        try {
+            doTunneling0();
+        } finally{
+            unlock();
+        }
+    }
+
+    private void doTunneling0() throws IOException {
         int retryTunnel = 0;
         String statusLine = "";
         int respCode = 0;
@@ -2068,6 +2134,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
         String proxyHost = null;
         int proxyPort = -1;
 
+        assert isLockHeldByCurrentThread();
+
         // save current requests so that they can be restored after tunnel is setup.
         MessageHeader savedRequests = requests;
         requests = new MessageHeader();
@@ -2273,7 +2341,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      * the connection.
      */
     @SuppressWarnings("fallthrough")
-    private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) {
+    private AuthenticationInfo getHttpProxyAuthentication(AuthenticationHeader authhdr) {
+
+        assert isLockHeldByCurrentThread();
+
         /* get authorization from authenticator */
         AuthenticationInfo ret = null;
         String raw = authhdr.raw();
@@ -2434,11 +2505,15 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     /**
      * Gets the authentication for an HTTP server, and applies it to
      * the connection.
-     * @param authHdr the AuthenticationHeader which tells what auth scheme is
+     * @param authhdr the AuthenticationHeader which tells what auth scheme is
      * preferred.
      */
     @SuppressWarnings("fallthrough")
-    private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) {
+    private AuthenticationInfo getServerAuthentication(AuthenticationHeader authhdr) {
+
+        // Only called from getInputStream0
+        assert isLockHeldByCurrentThread();
+
         /* get authorization from authenticator */
         AuthenticationInfo ret = null;
         String raw = authhdr.raw();
@@ -2716,6 +2791,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     private boolean followRedirect0(String loc, int stat, URL locUrl)
         throws IOException
     {
+        assert isLockHeldByCurrentThread();
+
         disconnectInternal();
         if (streaming()) {
             throw new HttpRetryException (RETRY_MSG3, stat, loc);
@@ -3195,17 +3272,22 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      * @param value the value to be set
      */
     @Override
-    public synchronized void setRequestProperty(String key, String value) {
-        if (connected || connecting)
-            throw new IllegalStateException("Already connected");
-        if (key == null)
-            throw new NullPointerException ("key is null");
+    public void setRequestProperty(String key, String value) {
+        lock();
+        try {
+            if (connected || connecting)
+                throw new IllegalStateException("Already connected");
+            if (key == null)
+                throw new NullPointerException("key is null");
 
-        if (isExternalMessageHeaderAllowed(key, value)) {
-            requests.set(key, value);
-            if (!key.equalsIgnoreCase("Content-Type")) {
-                userHeaders.set(key, value);
+            if (isExternalMessageHeaderAllowed(key, value)) {
+                requests.set(key, value);
+                if (!key.equalsIgnoreCase("Content-Type")) {
+                    userHeaders.set(key, value);
+                }
             }
+        } finally {
+            unlock();
         }
     }
 
@@ -3221,21 +3303,26 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      * @param   key     the keyword by which the request is known
      *                  (e.g., "<code>accept</code>").
      * @param   value  the value associated with it.
-     * @see #getRequestProperties(java.lang.String)
+     * @see #getRequestProperty(java.lang.String)
      * @since 1.4
      */
     @Override
-    public synchronized void addRequestProperty(String key, String value) {
-        if (connected || connecting)
-            throw new IllegalStateException("Already connected");
-        if (key == null)
-            throw new NullPointerException ("key is null");
+    public void addRequestProperty(String key, String value) {
+        lock();
+        try {
+            if (connected || connecting)
+                throw new IllegalStateException("Already connected");
+            if (key == null)
+                throw new NullPointerException("key is null");
 
-        if (isExternalMessageHeaderAllowed(key, value)) {
-            requests.add(key, value);
-            if (!key.equalsIgnoreCase("Content-Type")) {
+            if (isExternalMessageHeaderAllowed(key, value)) {
+                requests.add(key, value);
+                if (!key.equalsIgnoreCase("Content-Type")) {
                     userHeaders.add(key, value);
+                }
             }
+        } finally {
+            unlock();
         }
     }
 
@@ -3244,31 +3331,41 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
     // the connected test.
     //
     public void setAuthenticationProperty(String key, String value) {
+        // Only called by the implementation of AuthenticationInfo::setHeaders(...)
+        // in AuthenticationInfo subclasses, which is only called from
+        // methods from HttpURLConnection protected by the connectionLock.
+        assert isLockHeldByCurrentThread();
+
         checkMessageHeader(key, value);
         requests.set(key, value);
     }
 
     @Override
-    public synchronized String getRequestProperty (String key) {
-        if (key == null) {
-            return null;
-        }
-
-        // don't return headers containing security sensitive information
-        for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
-            if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
+    public String getRequestProperty (String key) {
+        lock();
+        try {
+            if (key == null) {
                 return null;
             }
-        }
-        if (!setUserCookies) {
-            if (key.equalsIgnoreCase("Cookie")) {
-                return userCookies;
+
+            // don't return headers containing security sensitive information
+            for (int i = 0; i < EXCLUDE_HEADERS.length; i++) {
+                if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
+                    return null;
+                }
             }
-            if (key.equalsIgnoreCase("Cookie2")) {
-                return userCookies2;
+            if (!setUserCookies) {
+                if (key.equalsIgnoreCase("Cookie")) {
+                    return userCookies;
+                }
+                if (key.equalsIgnoreCase("Cookie2")) {
+                    return userCookies2;
+                }
             }
+            return requests.findValue(key);
+        } finally {
+            unlock();
         }
-        return requests.findValue(key);
     }
 
     /**
@@ -3284,29 +3381,34 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      * @since 1.4
      */
     @Override
-    public synchronized Map<String, List<String>> getRequestProperties() {
-        if (connected)
-            throw new IllegalStateException("Already connected");
+    public Map<String, List<String>> getRequestProperties() {
+        lock();
+        try {
+            if (connected)
+                throw new IllegalStateException("Already connected");
 
-        // exclude headers containing security-sensitive info
-        if (setUserCookies) {
-            return requests.getHeaders(EXCLUDE_HEADERS);
-        }
-        /*
-         * The cookies in the requests message headers may have
-         * been modified. Use the saved user cookies instead.
-         */
-        Map<String, List<String>> userCookiesMap = null;
-        if (userCookies != null || userCookies2 != null) {
-            userCookiesMap = new HashMap<>();
-            if (userCookies != null) {
-                userCookiesMap.put("Cookie", Arrays.asList(userCookies));
+            // exclude headers containing security-sensitive info
+            if (setUserCookies) {
+                return requests.getHeaders(EXCLUDE_HEADERS);
             }
-            if (userCookies2 != null) {
-                userCookiesMap.put("Cookie2", Arrays.asList(userCookies2));
+            /*
+             * The cookies in the requests message headers may have
+             * been modified. Use the saved user cookies instead.
+             */
+            Map<String, List<String>> userCookiesMap = null;
+            if (userCookies != null || userCookies2 != null) {
+                userCookiesMap = new HashMap<>();
+                if (userCookies != null) {
+                    userCookiesMap.put("Cookie", Arrays.asList(userCookies));
+                }
+                if (userCookies2 != null) {
+                    userCookiesMap.put("Cookie2", Arrays.asList(userCookies2));
+                }
             }
+            return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
+        } finally {
+            unlock();
         }
-        return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
     }
 
     @Override
@@ -3350,7 +3452,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
      * value to be used in milliseconds
      * @throws IllegalArgumentException if the timeout parameter is negative
      *
-     * @see java.net.URLConnectiongetReadTimeout()
+     * @see java.net.URLConnection#getReadTimeout()
      * @see java.io.InputStream#read()
      * @since 1.5
      */
@@ -3469,6 +3571,9 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
          * @see     java.io.FilterInputStream#in
          * @see     java.io.FilterInputStream#reset()
          */
+        // safe to use synchronized here: super method is synchronized too
+        // and involves no blocking operation; only mark & reset are
+        // synchronized in the super class hierarchy.
         @Override
         public synchronized void mark(int readlimit) {
             super.mark(readlimit);
@@ -3499,6 +3604,9 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
          * @see        java.io.FilterInputStream#in
          * @see        java.io.FilterInputStream#mark(int)
          */
+        // safe to use synchronized here: super method is synchronized too
+        // and involves no blocking operation; only mark & reset are
+        // synchronized in the super class hierarchy.
         @Override
         public synchronized void reset() throws IOException {
             super.reset();
@@ -3679,8 +3787,14 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
             if (error) {
                 throw errorExcp;
             }
-            if (((PrintStream)out).checkError()) {
-                throw new IOException("Error writing request body to server");
+            if (out instanceof PrintStream) {
+                if (((PrintStream) out).checkError()) {
+                    throw new IOException("Error writing request body to server");
+                }
+            } else if (out instanceof ChunkedOutputStream) {
+                if (((ChunkedOutputStream) out).checkError()) {
+                    throw new IOException("Error writing request body to server");
+                }
             }
         }
 
diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java b/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java
index 27e535b0086..9223f3c7b1b 100644
--- a/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java
+++ b/src/java.base/share/classes/sun/net/www/protocol/http/NegotiateAuthentication.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2020, 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
@@ -30,6 +30,8 @@ import java.io.IOException;
 import java.net.Authenticator.RequestorType;
 import java.util.Base64;
 import java.util.HashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
 import sun.net.www.HeaderParser;
 import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
 import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
@@ -57,6 +59,8 @@ class NegotiateAuthentication extends AuthenticationInfo {
     // the cache can be used only once, so after the first use, it's cleaned.
     static HashMap <String, Boolean> supported = null;
     static ThreadLocal <HashMap <String, Negotiator>> cache = null;
+    private static final ReentrantLock negotiateLock = new ReentrantLock();
+
     /* Whether cache is enabled for Negotiate/Kerberos */
     private static final boolean cacheSPNEGO;
     static {
@@ -101,40 +105,50 @@ class NegotiateAuthentication extends AuthenticationInfo {
      *
      * @return true if supported
      */
-    synchronized public static boolean isSupported(HttpCallerInfo hci) {
-        if (supported == null) {
-            supported = new HashMap<>();
-        }
-        String hostname = hci.host;
-        hostname = hostname.toLowerCase();
-        if (supported.containsKey(hostname)) {
-            return supported.get(hostname);
-        }
-
-        Negotiator neg = Negotiator.getNegotiator(hci);
-        if (neg != null) {
-            supported.put(hostname, true);
-            // the only place cache.put is called. here we can make sure
-            // the object is valid and the oneToken inside is not null
-            if (cache == null) {
-                cache = new ThreadLocal<>() {
-                    @Override
-                    protected HashMap<String, Negotiator> initialValue() {
-                        return new HashMap<>();
-                    }
-                };
+    public static boolean isSupported(HttpCallerInfo hci) {
+        negotiateLock.lock();
+        try {
+            if (supported == null) {
+                supported = new HashMap<>();
             }
-            cache.get().put(hostname, neg);
-            return true;
-        } else {
-            supported.put(hostname, false);
-            return false;
+            String hostname = hci.host;
+            hostname = hostname.toLowerCase();
+            if (supported.containsKey(hostname)) {
+                return supported.get(hostname);
+            }
+
+            Negotiator neg = Negotiator.getNegotiator(hci);
+            if (neg != null) {
+                supported.put(hostname, true);
+                // the only place cache.put is called. here we can make sure
+                // the object is valid and the oneToken inside is not null
+                if (cache == null) {
+                    cache = new ThreadLocal<>() {
+                        @Override
+                        protected HashMap<String, Negotiator> initialValue() {
+                            return new HashMap<>();
+                        }
+                    };
+                }
+                cache.get().put(hostname, neg);
+                return true;
+            } else {
+                supported.put(hostname, false);
+                return false;
+            }
+        } finally {
+            negotiateLock.unlock();
         }
     }
 
-    private static synchronized HashMap<String, Negotiator> getCache() {
-        if (cache == null) return null;
-        return cache.get();
+    private static HashMap<String, Negotiator> getCache() {
+        negotiateLock.lock();
+        try {
+            if (cache == null) return null;
+            return cache.get();
+        } finally {
+            negotiateLock.unlock();
+        }
     }
 
     @Override
@@ -172,7 +186,10 @@ class NegotiateAuthentication extends AuthenticationInfo {
      * @return true if all goes well, false if no headers were set.
      */
     @Override
-    public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+    public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+        // no need to synchronize here:
+        //   already locked by s.n.w.p.h.HttpURLConnection
+        assert conn.isLockHeldByCurrentThread();
 
         try {
             String response;
diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java
index 9f1c992f8bc..59785590d02 100644
--- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java
+++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java
@@ -341,8 +341,10 @@ final class HttpsClient extends HttpClient
                 boolean compatible = ((ret.proxy != null && ret.proxy.equals(p)) ||
                     (ret.proxy == null && p == Proxy.NO_PROXY))
                      && Objects.equals(ret.getAuthenticatorKey(), ak);
+
                 if (compatible) {
-                    synchronized (ret) {
+                    ret.lock();
+                    try {
                         ret.cachedHttpClient = true;
                         assert ret.inCache;
                         ret.inCache = false;
@@ -351,18 +353,23 @@ final class HttpsClient extends HttpClient
                         if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
                             logger.finest("KeepAlive stream retrieved from the cache, " + ret);
                         }
+                    } finally {
+                        ret.unlock();
                     }
                 } else {
                     // We cannot return this connection to the cache as it's
                     // KeepAliveTimeout will get reset. We simply close the connection.
                     // This should be fine as it is very rare that a connection
                     // to the same host will not use the same proxy.
-                    synchronized(ret) {
+                    ret.lock();
+                    try {
                         if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
                             logger.finest("Not returning this connection to cache: " + ret);
                         }
                         ret.inCache = false;
                         ret.closeServer();
+                    } finally {
+                        ret.unlock();
                     }
                     ret = null;
                 }
diff --git a/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java b/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java
index 9901d799d1c..5b521101a22 100644
--- a/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java
+++ b/src/java.base/unix/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2020, 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
@@ -225,7 +225,10 @@ public class NTLMAuthentication extends AuthenticationInfo {
      * @return true if all goes well, false if no headers were set.
      */
     @Override
-    public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+    public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+        // no need to synchronize here:
+        //   already locked by s.n.w.p.h.HttpURLConnection
+        assert conn.isLockHeldByCurrentThread();
 
         try {
             String response;
diff --git a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java
index 1fec7349274..8433fabae46 100644
--- a/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java
+++ b/src/java.base/windows/classes/sun/net/www/protocol/http/ntlm/NTLMAuthentication.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2020, 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
@@ -244,7 +244,11 @@ public class NTLMAuthentication extends AuthenticationInfo {
      * @return true if all goes well, false if no headers were set.
      */
     @Override
-    public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+    public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
+
+        // no need to synchronize here:
+        //   already locked by s.n.w.p.h.HttpURLConnection
+        assert conn.isLockHeldByCurrentThread();
 
         try {
             NTLMAuthSequence seq = (NTLMAuthSequence)conn.authObj();