Index: lucene/core/src/test/org/apache/lucene/index/TestIndexWriterMerging.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/index/TestIndexWriterMerging.java	(revision 1536346)
+++ lucene/core/src/test/org/apache/lucene/index/TestIndexWriterMerging.java	(working copy)
@@ -311,19 +311,16 @@
   // merging a segment with >= 20 (maxMergeDocs) docs
   private class MyMergeScheduler extends MergeScheduler {
     @Override
-    synchronized public void merge(IndexWriter writer)
+    protected void mergeInternal(IndexWriter writer)
       throws IOException {
-
-      while(true) {
         MergePolicy.OneMerge merge = writer.getNextMerge();
         if (merge == null) {
-          break;
+          return;
         }
         for(int i=0;i<merge.segments.size();i++) {
           assert merge.segments.get(i).info.getDocCount() < 20;
         }
         writer.merge(merge);
-      }
     }
 
     @Override
Index: lucene/core/src/test/org/apache/lucene/TestMergeSchedulerExternal.java
===================================================================
--- lucene/core/src/test/org/apache/lucene/TestMergeSchedulerExternal.java	(revision 1536346)
+++ lucene/core/src/test/org/apache/lucene/TestMergeSchedulerExternal.java	(working copy)
@@ -16,23 +16,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import java.io.IOException;
-
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.MockDirectoryWrapper;
-import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.ConcurrentMergeScheduler;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.LogMergePolicy;
 import org.apache.lucene.index.MergePolicy;
-import org.apache.lucene.index.ConcurrentMergeScheduler;
+import org.apache.lucene.index.MergePolicy.OneMerge;
 import org.apache.lucene.index.MergeScheduler;
-import org.apache.lucene.index.MergePolicy.OneMerge;
-import org.apache.lucene.analysis.MockAnalyzer;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.MockDirectoryWrapper;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.LuceneTestCase;
 
+import java.io.IOException;
+
 /**
  * Holds tests cases to verify external APIs are accessible
  * while not being in org.apache.lucene.index package.
@@ -113,7 +113,7 @@
   private static class ReportingMergeScheduler extends MergeScheduler {
 
     @Override
-    public void merge(IndexWriter writer) throws IOException {
+    protected void mergeInternal(IndexWriter writer) throws IOException {
       OneMerge merge = null;
       while ((merge = writer.getNextMerge()) != null) {
         if (VERBOSE) {
Index: lucene/core/src/java/org/apache/lucene/index/NoMergeScheduler.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/index/NoMergeScheduler.java	(revision 1536346)
+++ lucene/core/src/java/org/apache/lucene/index/NoMergeScheduler.java	(working copy)
@@ -17,6 +17,8 @@
  * limitations under the License.
  */
 
+import java.io.IOException;
+
 /**
  * A {@link MergeScheduler} which never executes any merges. It is also a
  * singleton and can be accessed through {@link NoMergeScheduler#INSTANCE}. Use
@@ -43,6 +45,10 @@
   public void merge(IndexWriter writer) {}
 
   @Override
+  protected void mergeInternal(IndexWriter writer) throws IOException {
+  }
+
+  @Override
   public MergeScheduler clone() {
     return this;
   }
Index: lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java	(revision 1536346)
+++ lucene/core/src/java/org/apache/lucene/index/ConcurrentMergeScheduler.java	(working copy)
@@ -308,7 +308,7 @@
   }
 
   @Override
-  public synchronized void merge(IndexWriter writer) throws IOException {
+  protected synchronized final void mergeInternal(IndexWriter writer) throws IOException {   // needs to be synced for wait logic
 
     assert !Thread.holdsLock(writer);
 
Index: lucene/core/src/java/org/apache/lucene/index/MergeScheduler.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/index/MergeScheduler.java	(revision 1536346)
+++ lucene/core/src/java/org/apache/lucene/index/MergeScheduler.java	(working copy)
@@ -19,6 +19,8 @@
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 /** <p>Expert: {@link IndexWriter} uses an instance
  *  implementing this interface to execute the merges
@@ -31,14 +33,38 @@
 */
 public abstract class MergeScheduler implements Closeable, Cloneable {
 
+  private final Lock lock = new ReentrantLock();
+
   /** Sole constructor. (For invocation by subclass 
    *  constructors, typically implicit.) */
   protected MergeScheduler() {
   }
 
-  /** Run the merges provided by {@link IndexWriter#getNextMerge()}. */
-  public abstract void merge(IndexWriter writer) throws IOException;
+  /** Run the merges provided by {@link IndexWriter#getNextMerge()}. The calling thread will try to obtain a
+   * lock before it pulls a pending merge from the IndexWriter. If the thread
+   * can't obtain the lock it will return immediately. Even if the application is using
+   * multiple threads, only one merge may run at a time.  */
+  public void merge(IndexWriter writer) throws IOException {
+    do {
+      if (lock.tryLock()) {
+        /* no need to block if that lock is taken since some other thread pulls all the merges */
+        try {
+          mergeInternal(writer);
+        } finally {
+          lock.unlock();
+        }
+      } else {
+        /* if we reach that point it's guaranteed that another thread is merging already or at least
+           checking if there is a pending merge */
+        return;
+      }
+      /* Retry until the writer has no pending merges anymore - we release the lock to make sure another
+         thread "could" make progress and that we are not missing any concurrently added merge */
+    } while(writer.hasPendingMerges());
+  }
 
+  protected abstract void mergeInternal(IndexWriter writer) throws IOException;
+
   /** Close this MergeScheduler. */
   @Override
   public abstract void close() throws IOException;
Index: lucene/core/src/java/org/apache/lucene/index/SerialMergeScheduler.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/index/SerialMergeScheduler.java	(revision 1536346)
+++ lucene/core/src/java/org/apache/lucene/index/SerialMergeScheduler.java	(working copy)
@@ -27,16 +27,10 @@
   public SerialMergeScheduler() {
   }
 
-  /** Just do the merges in sequence. We do this
-   * "synchronized" so that even if the application is using
-   * multiple threads, only one merge may run at a time. */
-  @Override
-  synchronized public void merge(IndexWriter writer) throws IOException {
-
-    while(true) {
-      MergePolicy.OneMerge merge = writer.getNextMerge();
-      if (merge == null)
-        break;
+  /** Just do the merges in sequence. */
+  protected void mergeInternal(IndexWriter writer) throws IOException {
+    MergePolicy.OneMerge merge = writer.getNextMerge();
+    if (merge != null) {
       writer.merge(merge);
     }
   }
