diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
index 6b746e3193e2104f2799ec33559429d5a6179de7..731b4caa83c79f306fdcf4ff464c1c3fd06199c9 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
@@ -64,7 +64,6 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.util.IO;
 
 /**
  * Dumps a file over HTTP GET (or its information via HEAD).
@@ -122,7 +121,8 @@ long getLastModified() {
 	String getTailChecksum() throws IOException {
 		final int n = 20;
 		final byte[] buf = new byte[n];
-		IO.readFully(source.getChannel(), fileLen - n, buf, 0, n);
+		source.seek(fileLen - n);
+		source.readFully(buf, 0, n);
 		return ObjectId.fromRaw(buf).getName();
 	}
 
@@ -140,6 +140,7 @@ void serve(final HttpServletRequest req, final HttpServletResponse rsp,
 			final OutputStream out = rsp.getOutputStream();
 			try {
 				final byte[] buf = new byte[4096];
+				source.seek(pos);
 				while (pos < end) {
 					final int r = (int) Math.min(buf.length, end - pos);
 					final int n = source.read(buf, 0, r);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java
index 8f4e691630e2f7205365e07c7c808d90ce22c022..829832e6a5e5ca84f7e883b13fcbcbc5763ebb6f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java
@@ -65,7 +65,6 @@
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.PackInvalidException;
 import org.eclipse.jgit.errors.PackMismatchException;
-import org.eclipse.jgit.util.IO;
 import org.eclipse.jgit.util.NB;
 import org.eclipse.jgit.util.RawParseUtils;
 
@@ -90,6 +89,9 @@ public int compare(final PackFile a, final PackFile b) {
 
 	private RandomAccessFile fd;
 
+	/** Serializes reads performed against {@link #fd}. */
+	private final Object readLock = new Object();
+
 	long length;
 
 	private int activeWindows;
@@ -364,9 +366,11 @@ private void doOpen() throws IOException {
 		try {
 			if (invalid)
 				throw new PackInvalidException(packFile);
-			fd = new RandomAccessFile(packFile, "r");
-			length = fd.length();
-			onOpenPack();
+			synchronized (readLock) {
+				fd = new RandomAccessFile(packFile, "r");
+				length = fd.length();
+				onOpenPack();
+			}
 		} catch (IOException ioe) {
 			openFail();
 			throw ioe;
@@ -387,53 +391,61 @@ private void openFail() {
 	}
 
 	private void doClose() {
-		if (fd != null) {
-			try {
-				fd.close();
-			} catch (IOException err) {
-				// Ignore a close event. We had it open only for reading.
-				// There should not be errors related to network buffers
-				// not flushed, etc.
+		synchronized (readLock) {
+			if (fd != null) {
+				try {
+					fd.close();
+				} catch (IOException err) {
+					// Ignore a close event. We had it open only for reading.
+					// There should not be errors related to network buffers
+					// not flushed, etc.
+				}
+				fd = null;
 			}
-			fd = null;
 		}
 	}
 
 	ByteArrayWindow read(final long pos, int size) throws IOException {
-		if (length < pos + size)
-			size = (int) (length - pos);
-		final byte[] buf = new byte[size];
-		IO.readFully(fd.getChannel(), pos, buf, 0, size);
-		return new ByteArrayWindow(this, pos, buf);
+		synchronized (readLock) {
+			if (length < pos + size)
+				size = (int) (length - pos);
+			final byte[] buf = new byte[size];
+			fd.seek(pos);
+			fd.readFully(buf, 0, size);
+			return new ByteArrayWindow(this, pos, buf);
+		}
 	}
 
 	ByteWindow mmap(final long pos, int size) throws IOException {
-		if (length < pos + size)
-			size = (int) (length - pos);
+		synchronized (readLock) {
+			if (length < pos + size)
+				size = (int) (length - pos);
 
-		MappedByteBuffer map;
-		try {
-			map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
-		} catch (IOException ioe1) {
-			// The most likely reason this failed is the JVM has run out
-			// of virtual memory. We need to discard quickly, and try to
-			// force the GC to finalize and release any existing mappings.
-			//
-			System.gc();
-			System.runFinalization();
-			map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
-		}
+			MappedByteBuffer map;
+			try {
+				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
+			} catch (IOException ioe1) {
+				// The most likely reason this failed is the JVM has run out
+				// of virtual memory. We need to discard quickly, and try to
+				// force the GC to finalize and release any existing mappings.
+				//
+				System.gc();
+				System.runFinalization();
+				map = fd.getChannel().map(MapMode.READ_ONLY, pos, size);
+			}
 
-		if (map.hasArray())
-			return new ByteArrayWindow(this, pos, map.array());
-		return new ByteBufferWindow(this, pos, map);
+			if (map.hasArray())
+				return new ByteArrayWindow(this, pos, map.array());
+			return new ByteBufferWindow(this, pos, map);
+		}
 	}
 
 	private void onOpenPack() throws IOException {
 		final PackIndex idx = idx();
 		final byte[] buf = new byte[20];
 
-		IO.readFully(fd.getChannel(), 0, buf, 0, 12);
+		fd.seek(0);
+		fd.readFully(buf, 0, 12);
 		if (RawParseUtils.match(buf, 0, Constants.PACK_SIGNATURE) != 4)
 			throw new IOException(JGitText.get().notAPACKFile);
 		final long vers = NB.decodeUInt32(buf, 4);
@@ -445,7 +457,8 @@ private void onOpenPack() throws IOException {
 			throw new PackMismatchException(MessageFormat.format(
 					JGitText.get().packObjectCountMismatch, packCnt, idx.getObjectCount(), getPackFile()));
 
-		IO.readFully(fd.getChannel(), length - 20, buf, 0, 20);
+		fd.seek(length - 20);
+		fd.read(buf, 0, 20);
 		if (!Arrays.equals(buf, packChecksum))
 			throw new PackMismatchException(MessageFormat.format(
 					JGitText.get().packObjectCountMismatch
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
index 17786542034489a31916f30b26b4505e9a24ad78..1f2042d4c2d6e61446e5f06de19800e7132ed788 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
@@ -51,8 +51,6 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
 import java.text.MessageFormat;
 
 import org.eclipse.jgit.JGitText;
@@ -138,36 +136,6 @@ public static void readFully(final InputStream fd, final byte[] dst,
 		}
 	}
 
-	/**
-	 * Read the entire byte array into memory, or throw an exception.
-	 *
-	 * @param fd
-	 *            file to read the data from.
-	 * @param pos
-	 *            position to read from the file at.
-	 * @param dst
-	 *            buffer that must be fully populated, [off, off+len).
-	 * @param off
-	 *            position within the buffer to start writing to.
-	 * @param len
-	 *            number of bytes that must be read.
-	 * @throws EOFException
-	 *             the stream ended before dst was fully populated.
-	 * @throws IOException
-	 *             there was an error reading from the stream.
-	 */
-	public static void readFully(final FileChannel fd, long pos,
-			final byte[] dst, int off, int len) throws IOException {
-		while (len > 0) {
-			final int r = fd.read(ByteBuffer.wrap(dst, off, len), pos);
-			if (r <= 0)
-				throw new EOFException(JGitText.get().shortReadOfBlock);
-			pos += r;
-			off += r;
-			len -= r;
-		}
-	}
-
 	/**
 	 * Skip an entire region of an input stream.
 	 * <p>